mirror of
https://github.com/gitbucket/gitbucket.git
synced 2025-11-06 05:25:50 +01:00
Merge branch 'master' into newui-for-pullreq
This commit is contained in:
12
README.md
12
README.md
@@ -80,6 +80,18 @@ Run the following commands in `Terminal` to
|
|||||||
|
|
||||||
Release Notes
|
Release Notes
|
||||||
--------
|
--------
|
||||||
|
### 2.6 - COMING SOON!
|
||||||
|
- Search box at issues and pull requests
|
||||||
|
- Information from administrator
|
||||||
|
- Some bug fix and improvements
|
||||||
|
|
||||||
|
### 2.5 - 4 Nov 2014
|
||||||
|
- New Dashboard
|
||||||
|
- Change datetime format
|
||||||
|
- Create branch from Web UI
|
||||||
|
- Task list in Markdown
|
||||||
|
- Some bug fix and improvements
|
||||||
|
|
||||||
### 2.4.1 - 6 Oct 2014
|
### 2.4.1 - 6 Oct 2014
|
||||||
- Bug fix
|
- Bug fix
|
||||||
|
|
||||||
|
|||||||
2568
etc/icons.svg
2568
etc/icons.svg
File diff suppressed because it is too large
Load Diff
|
Before Width: | Height: | Size: 93 KiB After Width: | Height: | Size: 78 KiB |
@@ -1,18 +1,33 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import service._
|
import service._
|
||||||
import util.{UsersAuthenticator, Keys}
|
import util.{StringUtil, UsersAuthenticator, Keys}
|
||||||
import util.Implicits._
|
import util.Implicits._
|
||||||
|
import service.IssuesService.IssueSearchCondition
|
||||||
|
|
||||||
class DashboardController extends DashboardControllerBase
|
class DashboardController extends DashboardControllerBase
|
||||||
with IssuesService with PullRequestService with RepositoryService with AccountService
|
with IssuesService with PullRequestService with RepositoryService with AccountService
|
||||||
with UsersAuthenticator
|
with UsersAuthenticator
|
||||||
|
|
||||||
trait DashboardControllerBase extends ControllerBase {
|
trait DashboardControllerBase extends ControllerBase {
|
||||||
self: IssuesService with PullRequestService with RepositoryService with UsersAuthenticator =>
|
self: IssuesService with PullRequestService with RepositoryService with AccountService
|
||||||
|
with UsersAuthenticator =>
|
||||||
|
|
||||||
get("/dashboard/issues/repos")(usersOnly {
|
get("/dashboard/issues")(usersOnly {
|
||||||
searchIssues("all")
|
val q = request.getParameter("q")
|
||||||
|
val account = context.loginAccount.get
|
||||||
|
Option(q).map { q =>
|
||||||
|
val condition = IssueSearchCondition(q, Map[String, Int]())
|
||||||
|
q match {
|
||||||
|
case q if(q.contains("is:pr")) => redirect(s"/dashboard/pulls?q=${StringUtil.urlEncode(q)}")
|
||||||
|
case q if(q.contains(s"author:${account.userName}")) => redirect(s"/dashboard/issues/created_by${condition.toURL}")
|
||||||
|
case q if(q.contains(s"assignee:${account.userName}")) => redirect(s"/dashboard/issues/assigned${condition.toURL}")
|
||||||
|
case q if(q.contains(s"mentions:${account.userName}")) => redirect(s"/dashboard/issues/mentioned${condition.toURL}")
|
||||||
|
case _ => searchIssues("created_by")
|
||||||
|
}
|
||||||
|
} getOrElse {
|
||||||
|
searchIssues("created_by")
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/dashboard/issues/assigned")(usersOnly {
|
get("/dashboard/issues/assigned")(usersOnly {
|
||||||
@@ -23,87 +38,99 @@ trait DashboardControllerBase extends ControllerBase {
|
|||||||
searchIssues("created_by")
|
searchIssues("created_by")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
get("/dashboard/issues/mentioned")(usersOnly {
|
||||||
|
searchIssues("mentioned")
|
||||||
|
})
|
||||||
|
|
||||||
get("/dashboard/pulls")(usersOnly {
|
get("/dashboard/pulls")(usersOnly {
|
||||||
searchPullRequests("created_by", None)
|
val q = request.getParameter("q")
|
||||||
|
val account = context.loginAccount.get
|
||||||
|
Option(q).map { q =>
|
||||||
|
val condition = IssueSearchCondition(q, Map[String, Int]())
|
||||||
|
q match {
|
||||||
|
case q if(q.contains("is:issue")) => redirect(s"/dashboard/issues?q=${StringUtil.urlEncode(q)}")
|
||||||
|
case q if(q.contains(s"author:${account.userName}")) => redirect(s"/dashboard/pulls/created_by${condition.toURL}")
|
||||||
|
case q if(q.contains(s"assignee:${account.userName}")) => redirect(s"/dashboard/pulls/assigned${condition.toURL}")
|
||||||
|
case q if(q.contains(s"mentions:${account.userName}")) => redirect(s"/dashboard/pulls/mentioned${condition.toURL}")
|
||||||
|
case _ => searchPullRequests("created_by")
|
||||||
|
}
|
||||||
|
} getOrElse {
|
||||||
|
searchPullRequests("created_by")
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/dashboard/pulls/owned")(usersOnly {
|
get("/dashboard/pulls/created_by")(usersOnly {
|
||||||
searchPullRequests("created_by", None)
|
searchPullRequests("created_by")
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/dashboard/pulls/public")(usersOnly {
|
get("/dashboard/pulls/assigned")(usersOnly {
|
||||||
searchPullRequests("not_created_by", None)
|
searchPullRequests("assigned")
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/dashboard/pulls/for/:owner/:repository")(usersOnly {
|
get("/dashboard/pulls/mentioned")(usersOnly {
|
||||||
searchPullRequests("all", Some(params("owner") + "/" + params("repository")))
|
searchPullRequests("mentioned")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
private def getOrCreateCondition(key: String, filter: String, userName: String) = {
|
||||||
|
val condition = session.putAndGet(key, if(request.hasQueryString){
|
||||||
|
val q = request.getParameter("q")
|
||||||
|
if(q == null){
|
||||||
|
IssueSearchCondition(request)
|
||||||
|
} else {
|
||||||
|
IssueSearchCondition(q, Map[String, Int]())
|
||||||
|
}
|
||||||
|
} else session.getAs[IssueSearchCondition](key).getOrElse(IssueSearchCondition()))
|
||||||
|
|
||||||
|
filter match {
|
||||||
|
case "assigned" => condition.copy(assigned = Some(userName), author = None , mentioned = None)
|
||||||
|
case "mentioned" => condition.copy(assigned = None , author = None , mentioned = Some(userName))
|
||||||
|
case _ => condition.copy(assigned = None , author = Some(userName), mentioned = None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private def searchIssues(filter: String) = {
|
private def searchIssues(filter: String) = {
|
||||||
import IssuesService._
|
import IssuesService._
|
||||||
|
|
||||||
// condition
|
|
||||||
val condition = session.putAndGet(Keys.Session.DashboardIssues,
|
|
||||||
if(request.hasQueryString) IssueSearchCondition(request)
|
|
||||||
else session.getAs[IssueSearchCondition](Keys.Session.DashboardIssues).getOrElse(IssueSearchCondition())
|
|
||||||
)
|
|
||||||
|
|
||||||
val userName = context.loginAccount.get.userName
|
val userName = context.loginAccount.get.userName
|
||||||
|
val condition = getOrCreateCondition(Keys.Session.DashboardIssues, filter, userName)
|
||||||
val userRepos = getUserRepositories(userName, context.baseUrl, true).map(repo => repo.owner -> repo.name)
|
val userRepos = getUserRepositories(userName, context.baseUrl, true).map(repo => repo.owner -> repo.name)
|
||||||
val filterUser = Map(filter -> userName)
|
|
||||||
val page = IssueSearchCondition.page(request)
|
val page = IssueSearchCondition.page(request)
|
||||||
|
|
||||||
dashboard.html.issues(
|
dashboard.html.issues(
|
||||||
dashboard.html.issueslist(
|
searchIssue(condition, false, (page - 1) * IssueLimit, IssueLimit, userRepos: _*),
|
||||||
searchIssue(condition, filterUser, false, (page - 1) * IssueLimit, IssueLimit, userRepos: _*),
|
|
||||||
page,
|
page,
|
||||||
countIssue(condition.copy(state = "open" ), filterUser, false, userRepos: _*),
|
countIssue(condition.copy(state = "open" ), false, userRepos: _*),
|
||||||
countIssue(condition.copy(state = "closed"), filterUser, false, userRepos: _*),
|
countIssue(condition.copy(state = "closed"), false, userRepos: _*),
|
||||||
condition),
|
filter match {
|
||||||
countIssue(condition.copy(assigned = None, author = None), filterUser, false, userRepos: _*),
|
case "assigned" => condition.copy(assigned = Some(userName))
|
||||||
countIssue(condition.copy(assigned = Some(userName), author = None), filterUser, false, userRepos: _*),
|
case "mentioned" => condition.copy(mentioned = Some(userName))
|
||||||
countIssue(condition.copy(assigned = None, author = Some(userName)), filterUser, false, userRepos: _*),
|
case _ => condition.copy(author = Some(userName))
|
||||||
countIssueGroupByRepository(condition, filterUser, false, userRepos: _*),
|
},
|
||||||
condition,
|
filter,
|
||||||
filter)
|
getGroupNames(userName))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private def searchPullRequests(filter: String, repository: Option[String]) = {
|
private def searchPullRequests(filter: String) = {
|
||||||
import IssuesService._
|
import IssuesService._
|
||||||
import PullRequestService._
|
import PullRequestService._
|
||||||
|
|
||||||
// condition
|
|
||||||
val condition = session.putAndGet(Keys.Session.DashboardPulls, {
|
|
||||||
if(request.hasQueryString) IssueSearchCondition(request)
|
|
||||||
else session.getAs[IssueSearchCondition](Keys.Session.DashboardPulls).getOrElse(IssueSearchCondition())
|
|
||||||
}.copy(repo = repository))
|
|
||||||
|
|
||||||
val userName = context.loginAccount.get.userName
|
val userName = context.loginAccount.get.userName
|
||||||
|
val condition = getOrCreateCondition(Keys.Session.DashboardPulls, filter, userName)
|
||||||
val allRepos = getAllRepositories(userName)
|
val allRepos = getAllRepositories(userName)
|
||||||
val userRepos = getUserRepositories(userName, context.baseUrl, true).map(repo => repo.owner -> repo.name)
|
|
||||||
val filterUser = Map(filter -> userName)
|
|
||||||
val page = IssueSearchCondition.page(request)
|
val page = IssueSearchCondition.page(request)
|
||||||
|
|
||||||
val counts = countIssueGroupByRepository(
|
|
||||||
IssueSearchCondition().copy(state = condition.state), filterUser, true, userRepos: _*)
|
|
||||||
|
|
||||||
dashboard.html.pulls(
|
dashboard.html.pulls(
|
||||||
dashboard.html.pullslist(
|
searchIssue(condition, true, (page - 1) * PullRequestLimit, PullRequestLimit, allRepos: _*),
|
||||||
searchIssue(condition, filterUser, true, (page - 1) * PullRequestLimit, PullRequestLimit, allRepos: _*),
|
|
||||||
page,
|
page,
|
||||||
countIssue(condition.copy(state = "open" ), filterUser, true, allRepos: _*),
|
countIssue(condition.copy(state = "open" ), true, allRepos: _*),
|
||||||
countIssue(condition.copy(state = "closed"), filterUser, true, allRepos: _*),
|
countIssue(condition.copy(state = "closed"), true, allRepos: _*),
|
||||||
condition,
|
filter match {
|
||||||
None,
|
case "assigned" => condition.copy(assigned = Some(userName))
|
||||||
false),
|
case "mentioned" => condition.copy(mentioned = Some(userName))
|
||||||
getAllPullRequestCountGroupByUser(condition.state == "closed", userName),
|
case _ => condition.copy(author = Some(userName))
|
||||||
userRepos.map { case (userName, repoName) =>
|
},
|
||||||
(userName, repoName, counts.find { x => x._1 == userName && x._2 == repoName }.map(_._3).getOrElse(0))
|
filter,
|
||||||
}.sortBy(_._3).reverse,
|
getGroupNames(userName))
|
||||||
condition,
|
|
||||||
filter)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import util.Implicits._
|
|||||||
import util.ControlUtil._
|
import util.ControlUtil._
|
||||||
import org.scalatra.Ok
|
import org.scalatra.Ok
|
||||||
import model.Issue
|
import model.Issue
|
||||||
import plugin.PluginSystem
|
|
||||||
|
|
||||||
class IssuesController extends IssuesControllerBase
|
class IssuesController extends IssuesControllerBase
|
||||||
with IssuesService with RepositoryService with AccountService with LabelsService with MilestonesService with ActivityService
|
with IssuesService with RepositoryService with AccountService with LabelsService with MilestonesService with ActivityService
|
||||||
@@ -50,7 +49,12 @@ trait IssuesControllerBase extends ControllerBase {
|
|||||||
)(IssueStateForm.apply)
|
)(IssueStateForm.apply)
|
||||||
|
|
||||||
get("/:owner/:repository/issues")(referrersOnly { repository =>
|
get("/:owner/:repository/issues")(referrersOnly { repository =>
|
||||||
|
val q = request.getParameter("q")
|
||||||
|
if(Option(q).exists(_.contains("is:pr"))){
|
||||||
|
redirect(s"/${repository.owner}/${repository.name}/pulls?q=" + StringUtil.urlEncode(q))
|
||||||
|
} else {
|
||||||
searchIssues(repository)
|
searchIssues(repository)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/issues/:id")(referrersOnly { repository =>
|
get("/:owner/:repository/issues/:id")(referrersOnly { repository =>
|
||||||
@@ -195,7 +199,7 @@ trait IssuesControllerBase extends ControllerBase {
|
|||||||
org.json4s.jackson.Serialization.write(
|
org.json4s.jackson.Serialization.write(
|
||||||
Map("title" -> x.title,
|
Map("title" -> x.title,
|
||||||
"content" -> view.Markdown.toHtml(x.content getOrElse "No description given.",
|
"content" -> view.Markdown.toHtml(x.content getOrElse "No description given.",
|
||||||
repository, false, true)
|
repository, false, true, true, isEditable(x.userName, x.repositoryName, x.openedUserName))
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
} else Unauthorized
|
} else Unauthorized
|
||||||
@@ -212,7 +216,7 @@ trait IssuesControllerBase extends ControllerBase {
|
|||||||
contentType = formats("json")
|
contentType = formats("json")
|
||||||
org.json4s.jackson.Serialization.write(
|
org.json4s.jackson.Serialization.write(
|
||||||
Map("content" -> view.Markdown.toHtml(x.content,
|
Map("content" -> view.Markdown.toHtml(x.content,
|
||||||
repository, false, true)
|
repository, false, true, true, isEditable(x.userName, x.repositoryName, x.commentedUserName))
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
} else Unauthorized
|
} else Unauthorized
|
||||||
@@ -390,19 +394,25 @@ trait IssuesControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
// retrieve search condition
|
// retrieve search condition
|
||||||
val condition = session.putAndGet(sessionKey,
|
val condition = session.putAndGet(sessionKey,
|
||||||
if(request.hasQueryString) IssueSearchCondition(request)
|
if(request.hasQueryString){
|
||||||
else session.getAs[IssueSearchCondition](sessionKey).getOrElse(IssueSearchCondition())
|
val q = request.getParameter("q")
|
||||||
|
if(q == null){
|
||||||
|
IssueSearchCondition(request)
|
||||||
|
} else {
|
||||||
|
IssueSearchCondition(q, getMilestones(owner, repoName).map(x => (x.title, x.milestoneId)).toMap)
|
||||||
|
}
|
||||||
|
} else session.getAs[IssueSearchCondition](sessionKey).getOrElse(IssueSearchCondition())
|
||||||
)
|
)
|
||||||
|
|
||||||
issues.html.list(
|
issues.html.list(
|
||||||
"issues",
|
"issues",
|
||||||
searchIssue(condition, Map.empty, false, (page - 1) * IssueLimit, IssueLimit, owner -> repoName),
|
searchIssue(condition, false, (page - 1) * IssueLimit, IssueLimit, owner -> repoName),
|
||||||
page,
|
page,
|
||||||
(getCollaborators(owner, repoName) :+ owner).sorted,
|
(getCollaborators(owner, repoName) :+ owner).sorted,
|
||||||
getMilestones(owner, repoName),
|
getMilestones(owner, repoName),
|
||||||
getLabels(owner, repoName),
|
getLabels(owner, repoName),
|
||||||
countIssue(condition.copy(state = "open" ), Map.empty, false, owner -> repoName),
|
countIssue(condition.copy(state = "open" ), false, owner -> repoName),
|
||||||
countIssue(condition.copy(state = "closed"), Map.empty, false, owner -> repoName),
|
countIssue(condition.copy(state = "closed"), false, owner -> repoName),
|
||||||
condition,
|
condition,
|
||||||
repository,
|
repository,
|
||||||
hasWritePermission(owner, repoName, context.loginAccount))
|
hasWritePermission(owner, repoName, context.loginAccount))
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import util.{LockUtil, CollaboratorsAuthenticator, JGitUtil, ReferrerAuthenticator, Notifier, Keys}
|
import util._
|
||||||
import util.Directory._
|
import util.Directory._
|
||||||
import util.Implicits._
|
import util.Implicits._
|
||||||
import util.ControlUtil._
|
import util.ControlUtil._
|
||||||
@@ -18,6 +18,9 @@ import org.slf4j.LoggerFactory
|
|||||||
import org.eclipse.jgit.merge.MergeStrategy
|
import org.eclipse.jgit.merge.MergeStrategy
|
||||||
import org.eclipse.jgit.errors.NoMergeBaseException
|
import org.eclipse.jgit.errors.NoMergeBaseException
|
||||||
import service.WebHookService.WebHookPayload
|
import service.WebHookService.WebHookPayload
|
||||||
|
import util.JGitUtil.DiffInfo
|
||||||
|
import scala.Some
|
||||||
|
import util.JGitUtil.CommitInfo
|
||||||
|
|
||||||
class PullRequestsController extends PullRequestsControllerBase
|
class PullRequestsController extends PullRequestsControllerBase
|
||||||
with RepositoryService with AccountService with IssuesService with PullRequestService with MilestonesService with LabelsService
|
with RepositoryService with AccountService with IssuesService with PullRequestService with MilestonesService with LabelsService
|
||||||
@@ -59,7 +62,12 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
case class MergeForm(message: String)
|
case class MergeForm(message: String)
|
||||||
|
|
||||||
get("/:owner/:repository/pulls")(referrersOnly { repository =>
|
get("/:owner/:repository/pulls")(referrersOnly { repository =>
|
||||||
|
val q = request.getParameter("q")
|
||||||
|
if(Option(q).exists(_.contains("is:issue"))){
|
||||||
|
redirect(s"/${repository.owner}/${repository.name}/issues?q=" + StringUtil.urlEncode(q))
|
||||||
|
} else {
|
||||||
searchPullRequests(None, repository)
|
searchPullRequests(None, repository)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/pull/:id")(referrersOnly { repository =>
|
get("/:owner/:repository/pull/:id")(referrersOnly { repository =>
|
||||||
@@ -460,13 +468,13 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
issues.html.list(
|
issues.html.list(
|
||||||
"pulls",
|
"pulls",
|
||||||
searchIssue(condition, Map.empty, true, (page - 1) * PullRequestLimit, PullRequestLimit, owner -> repoName),
|
searchIssue(condition, true, (page - 1) * PullRequestLimit, PullRequestLimit, owner -> repoName),
|
||||||
page,
|
page,
|
||||||
(getCollaborators(owner, repoName) :+ owner).sorted,
|
(getCollaborators(owner, repoName) :+ owner).sorted,
|
||||||
getMilestones(owner, repoName),
|
getMilestones(owner, repoName),
|
||||||
getLabels(owner, repoName),
|
getLabels(owner, repoName),
|
||||||
countIssue(condition.copy(state = "open" ), Map.empty, true, owner -> repoName),
|
countIssue(condition.copy(state = "open" ), true, owner -> repoName),
|
||||||
countIssue(condition.copy(state = "closed"), Map.empty, true, owner -> repoName),
|
countIssue(condition.copy(state = "closed"), true, owner -> repoName),
|
||||||
condition,
|
condition,
|
||||||
repository,
|
repository,
|
||||||
hasWritePermission(owner, repoName, context.loginAccount))
|
hasWritePermission(owner, repoName, context.loginAccount))
|
||||||
|
|||||||
@@ -77,7 +77,9 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
contentType = "text/html"
|
contentType = "text/html"
|
||||||
view.helpers.markdown(params("content"), repository,
|
view.helpers.markdown(params("content"), repository,
|
||||||
params("enableWikiLink").toBoolean,
|
params("enableWikiLink").toBoolean,
|
||||||
params("enableRefsLink").toBoolean)
|
params("enableRefsLink").toBoolean,
|
||||||
|
params("enableTaskList").toBoolean,
|
||||||
|
hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -112,7 +114,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
repo.html.commits(if(path.isEmpty) Nil else path.split("/").toList, branchName, repository,
|
repo.html.commits(if(path.isEmpty) Nil else path.split("/").toList, branchName, repository,
|
||||||
logs.splitWith{ (commit1, commit2) =>
|
logs.splitWith{ (commit1, commit2) =>
|
||||||
view.helpers.date(commit1.commitTime) == view.helpers.date(commit2.commitTime)
|
view.helpers.date(commit1.commitTime) == view.helpers.date(commit2.commitTime)
|
||||||
}, page, hasNext)
|
}, page, hasNext, hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
||||||
case Left(_) => NotFound
|
case Left(_) => NotFound
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -239,6 +241,24 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a branch.
|
||||||
|
*/
|
||||||
|
post("/:owner/:repository/branches")(collaboratorsOnly { repository =>
|
||||||
|
val newBranchName = params.getOrElse("new", halt(400))
|
||||||
|
val fromBranchName = params.getOrElse("from", halt(400))
|
||||||
|
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
||||||
|
JGitUtil.createBranch(git, fromBranchName, newBranchName)
|
||||||
|
} match {
|
||||||
|
case Right(message) =>
|
||||||
|
flash += "info" -> message
|
||||||
|
redirect(s"/${repository.owner}/${repository.name}/tree/${StringUtil.urlEncode(newBranchName).replace("%2F", "/")}")
|
||||||
|
case Left(message) =>
|
||||||
|
flash += "error" -> message
|
||||||
|
redirect(s"/${repository.owner}/${repository.name}/tree/${fromBranchName}")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes branch.
|
* Deletes branch.
|
||||||
*/
|
*/
|
||||||
@@ -331,7 +351,8 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
repo.html.files(revision, repository,
|
repo.html.files(revision, repository,
|
||||||
if(path == ".") Nil else path.split("/").toList, // current path
|
if(path == ".") Nil else path.split("/").toList, // current path
|
||||||
new JGitUtil.CommitInfo(lastModifiedCommit), // last modified commit
|
new JGitUtil.CommitInfo(lastModifiedCommit), // last modified commit
|
||||||
files, readme, hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
files, readme, hasWritePermission(repository.owner, repository.name, context.loginAccount),
|
||||||
|
flash.get("info"), flash.get("error"))
|
||||||
}
|
}
|
||||||
} getOrElse NotFound
|
} getOrElse NotFound
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ trait SystemSettingsControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
private val form = mapping(
|
private val form = mapping(
|
||||||
"baseUrl" -> trim(label("Base URL", optional(text()))),
|
"baseUrl" -> trim(label("Base URL", optional(text()))),
|
||||||
|
"information" -> trim(label("Information", optional(text()))),
|
||||||
"allowAccountRegistration" -> trim(label("Account registration", boolean())),
|
"allowAccountRegistration" -> trim(label("Account registration", boolean())),
|
||||||
"gravatar" -> trim(label("Gravatar", boolean())),
|
"gravatar" -> trim(label("Gravatar", boolean())),
|
||||||
"notification" -> trim(label("Notification", boolean())),
|
"notification" -> trim(label("Notification", boolean())),
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ trait UserManagementControllerBase extends AccountManagementControllerBase {
|
|||||||
"url" -> trim(label("URL" ,optional(text(maxlength(200))))),
|
"url" -> trim(label("URL" ,optional(text(maxlength(200))))),
|
||||||
"fileId" -> trim(label("File ID" ,optional(text()))),
|
"fileId" -> trim(label("File ID" ,optional(text()))),
|
||||||
"clearImage" -> trim(label("Clear image" ,boolean())),
|
"clearImage" -> trim(label("Clear image" ,boolean())),
|
||||||
"removed" -> trim(label("Disable" ,boolean()))
|
"removed" -> trim(label("Disable" ,boolean(disableByNotYourself("userName"))))
|
||||||
)(EditUserForm.apply)
|
)(EditUserForm.apply)
|
||||||
|
|
||||||
val newGroupForm = mapping(
|
val newGroupForm = mapping(
|
||||||
@@ -190,4 +190,14 @@ trait UserManagementControllerBase extends AccountManagementControllerBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected def disableByNotYourself(paramName: String): Constraint = new Constraint() {
|
||||||
|
override def validate(name: String, value: String, messages: Messages): Option[String] = {
|
||||||
|
params.get(paramName).flatMap { userName =>
|
||||||
|
if(userName == context.loginAccount.get.userName)
|
||||||
|
Some("You can't disable your account yourself")
|
||||||
|
else
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -168,6 +168,11 @@ trait AccountService {
|
|||||||
Repositories.filter(_.userName === userName.bind).delete
|
Repositories.filter(_.userName === userName.bind).delete
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def getGroupNames(userName: String)(implicit s: Session): List[String] = {
|
||||||
|
List(userName) ++
|
||||||
|
Collaborators.filter(_.collaboratorName === userName.bind).sortBy(_.userName).map(_.userName).list
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
object AccountService extends AccountService
|
object AccountService extends AccountService
|
||||||
|
|||||||
@@ -47,9 +47,9 @@ trait IssuesService {
|
|||||||
* @param repos Tuple of the repository owner and the repository name
|
* @param repos Tuple of the repository owner and the repository name
|
||||||
* @return the count of the search result
|
* @return the count of the search result
|
||||||
*/
|
*/
|
||||||
def countIssue(condition: IssueSearchCondition, filterUser: Map[String, String], onlyPullRequest: Boolean,
|
def countIssue(condition: IssueSearchCondition, onlyPullRequest: Boolean,
|
||||||
repos: (String, String)*)(implicit s: Session): Int =
|
repos: (String, String)*)(implicit s: Session): Int =
|
||||||
Query(searchIssueQuery(repos, condition, filterUser, onlyPullRequest).length).first
|
Query(searchIssueQuery(repos, condition, onlyPullRequest).length).first
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the Map which contains issue count for each labels.
|
* Returns the Map which contains issue count for each labels.
|
||||||
@@ -62,7 +62,7 @@ trait IssuesService {
|
|||||||
def countIssueGroupByLabels(owner: String, repository: String, condition: IssueSearchCondition,
|
def countIssueGroupByLabels(owner: String, repository: String, condition: IssueSearchCondition,
|
||||||
filterUser: Map[String, String])(implicit s: Session): Map[String, Int] = {
|
filterUser: Map[String, String])(implicit s: Session): Map[String, Int] = {
|
||||||
|
|
||||||
searchIssueQuery(Seq(owner -> repository), condition.copy(labels = Set.empty), filterUser, false)
|
searchIssueQuery(Seq(owner -> repository), condition.copy(labels = Set.empty), false)
|
||||||
.innerJoin(IssueLabels).on { (t1, t2) =>
|
.innerJoin(IssueLabels).on { (t1, t2) =>
|
||||||
t1.byIssue(t2.userName, t2.repositoryName, t2.issueId)
|
t1.byIssue(t2.userName, t2.repositoryName, t2.issueId)
|
||||||
}
|
}
|
||||||
@@ -77,46 +77,22 @@ trait IssuesService {
|
|||||||
}
|
}
|
||||||
.toMap
|
.toMap
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* Returns list which contains issue count for each repository.
|
|
||||||
* If the issue does not exist, its repository is not included in the result.
|
|
||||||
*
|
|
||||||
* @param condition the search condition
|
|
||||||
* @param onlyPullRequest if true then returns only pull request, false then returns both of issue and pull request.
|
|
||||||
* @param repos Tuple of the repository owner and the repository name
|
|
||||||
* @return list which contains issue count for each repository
|
|
||||||
*/
|
|
||||||
def countIssueGroupByRepository(
|
|
||||||
condition: IssueSearchCondition, filterUser: Map[String, String], onlyPullRequest: Boolean,
|
|
||||||
repos: (String, String)*)(implicit s: Session): List[(String, String, Int)] = {
|
|
||||||
searchIssueQuery(repos, condition.copy(repo = None), filterUser, onlyPullRequest)
|
|
||||||
.groupBy { t =>
|
|
||||||
t.userName -> t.repositoryName
|
|
||||||
}
|
|
||||||
.map { case (repo, t) =>
|
|
||||||
(repo._1, repo._2, t.length)
|
|
||||||
}
|
|
||||||
.sortBy(_._3 desc)
|
|
||||||
.list
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the search result against issues.
|
* Returns the search result against issues.
|
||||||
*
|
*
|
||||||
* @param condition the search condition
|
* @param condition the search condition
|
||||||
* @param filterUser the filter user name (key is "all", "assigned", "created_by" or "not_created_by", value is the user name)
|
|
||||||
* @param pullRequest if true then returns only pull requests, false then returns only issues.
|
* @param pullRequest if true then returns only pull requests, false then returns only issues.
|
||||||
* @param offset the offset for pagination
|
* @param offset the offset for pagination
|
||||||
* @param limit the limit for pagination
|
* @param limit the limit for pagination
|
||||||
* @param repos Tuple of the repository owner and the repository name
|
* @param repos Tuple of the repository owner and the repository name
|
||||||
* @return the search result (list of tuples which contain issue, labels and comment count)
|
* @return the search result (list of tuples which contain issue, labels and comment count)
|
||||||
*/
|
*/
|
||||||
def searchIssue(condition: IssueSearchCondition, filterUser: Map[String, String], pullRequest: Boolean,
|
def searchIssue(condition: IssueSearchCondition, pullRequest: Boolean, offset: Int, limit: Int, repos: (String, String)*)
|
||||||
offset: Int, limit: Int, repos: (String, String)*)
|
|
||||||
(implicit s: Session): List[IssueInfo] = {
|
(implicit s: Session): List[IssueInfo] = {
|
||||||
|
|
||||||
// get issues and comment count and labels
|
// get issues and comment count and labels
|
||||||
searchIssueQuery(repos, condition, filterUser, pullRequest)
|
searchIssueQuery(repos, condition, pullRequest)
|
||||||
.innerJoin(IssueOutline).on { (t1, t2) => t1.byIssue(t2.userName, t2.repositoryName, t2.issueId) }
|
.innerJoin(IssueOutline).on { (t1, t2) => t1.byIssue(t2.userName, t2.repositoryName, t2.issueId) }
|
||||||
.sortBy { case (t1, t2) =>
|
.sortBy { case (t1, t2) =>
|
||||||
(condition.sort match {
|
(condition.sort match {
|
||||||
@@ -157,23 +133,18 @@ trait IssuesService {
|
|||||||
/**
|
/**
|
||||||
* Assembles query for conditional issue searching.
|
* Assembles query for conditional issue searching.
|
||||||
*/
|
*/
|
||||||
private def searchIssueQuery(repos: Seq[(String, String)], condition: IssueSearchCondition,
|
private def searchIssueQuery(repos: Seq[(String, String)], condition: IssueSearchCondition, pullRequest: Boolean)(implicit s: Session) =
|
||||||
filterUser: Map[String, String], pullRequest: Boolean)(implicit s: Session) =
|
|
||||||
Issues filter { t1 =>
|
Issues filter { t1 =>
|
||||||
condition.repo
|
repos
|
||||||
.map { _.split('/') match { case array => Seq(array(0) -> array(1)) } }
|
|
||||||
.getOrElse (repos)
|
|
||||||
.map { case (owner, repository) => t1.byRepository(owner, repository) }
|
.map { case (owner, repository) => t1.byRepository(owner, repository) }
|
||||||
.foldLeft[Column[Boolean]](false) ( _ || _ ) &&
|
.foldLeft[Column[Boolean]](false) ( _ || _ ) &&
|
||||||
(t1.closed === (condition.state == "closed").bind) &&
|
(t1.closed === (condition.state == "closed").bind) &&
|
||||||
(t1.milestoneId === condition.milestoneId.get.get.bind, condition.milestoneId.flatten.isDefined) &&
|
(t1.milestoneId === condition.milestoneId.get.get.bind, condition.milestoneId.flatten.isDefined) &&
|
||||||
(t1.milestoneId.? isEmpty, condition.milestoneId == Some(None)) &&
|
(t1.milestoneId.? isEmpty, condition.milestoneId == Some(None)) &&
|
||||||
(t1.assignedUserName === filterUser("assigned").bind, filterUser.get("assigned").isDefined) &&
|
|
||||||
(t1.openedUserName === filterUser("created_by").bind, filterUser.get("created_by").isDefined) &&
|
|
||||||
(t1.openedUserName =!= filterUser("not_created_by").bind, filterUser.get("not_created_by").isDefined) &&
|
|
||||||
(t1.assignedUserName === condition.assigned.get.bind, condition.assigned.isDefined) &&
|
(t1.assignedUserName === condition.assigned.get.bind, condition.assigned.isDefined) &&
|
||||||
(t1.openedUserName === condition.author.get.bind, condition.author.isDefined) &&
|
(t1.openedUserName === condition.author.get.bind, condition.author.isDefined) &&
|
||||||
(t1.pullRequest === pullRequest.bind) &&
|
(t1.pullRequest === pullRequest.bind) &&
|
||||||
|
// Label filter
|
||||||
(IssueLabels filter { t2 =>
|
(IssueLabels filter { t2 =>
|
||||||
(t2.byIssue(t1.userName, t1.repositoryName, t1.issueId)) &&
|
(t2.byIssue(t1.userName, t1.repositoryName, t1.issueId)) &&
|
||||||
(t2.labelId in
|
(t2.labelId in
|
||||||
@@ -181,7 +152,19 @@ trait IssuesService {
|
|||||||
(t3.byRepository(t1.userName, t1.repositoryName)) &&
|
(t3.byRepository(t1.userName, t1.repositoryName)) &&
|
||||||
(t3.labelName inSetBind condition.labels)
|
(t3.labelName inSetBind condition.labels)
|
||||||
} map(_.labelId)))
|
} map(_.labelId)))
|
||||||
} exists, condition.labels.nonEmpty)
|
} exists, condition.labels.nonEmpty) &&
|
||||||
|
// Visibility filter
|
||||||
|
(Repositories filter { t2 =>
|
||||||
|
(t2.byRepository(t1.userName, t1.repositoryName)) &&
|
||||||
|
(t2.isPrivate === (condition.visibility == Some("private")).bind)
|
||||||
|
} exists, condition.visibility.nonEmpty) &&
|
||||||
|
// Organization (group) filter
|
||||||
|
(t1.userName inSetBind condition.groups, condition.groups.nonEmpty) &&
|
||||||
|
// Mentioned filter
|
||||||
|
((t1.openedUserName === condition.mentioned.get.bind) || t1.assignedUserName === condition.mentioned.get.bind ||
|
||||||
|
(IssueComments filter { t2 =>
|
||||||
|
(t2.byIssue(t1.userName, t1.repositoryName, t1.issueId)) && (t2.commentedUserName === condition.mentioned.get.bind)
|
||||||
|
} exists), condition.mentioned.isDefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
def createIssue(owner: String, repository: String, loginUser: String, title: String, content: Option[String],
|
def createIssue(owner: String, repository: String, loginUser: String, title: String, content: Option[String],
|
||||||
@@ -340,31 +323,62 @@ object IssuesService {
|
|||||||
milestoneId: Option[Option[Int]] = None,
|
milestoneId: Option[Option[Int]] = None,
|
||||||
author: Option[String] = None,
|
author: Option[String] = None,
|
||||||
assigned: Option[String] = None,
|
assigned: Option[String] = None,
|
||||||
repo: Option[String] = None,
|
mentioned: Option[String] = None,
|
||||||
state: String = "open",
|
state: String = "open",
|
||||||
sort: String = "created",
|
sort: String = "created",
|
||||||
direction: String = "desc"){
|
direction: String = "desc",
|
||||||
|
visibility: Option[String] = None,
|
||||||
|
groups: Set[String] = Set.empty){
|
||||||
|
|
||||||
def isEmpty: Boolean = {
|
def isEmpty: Boolean = {
|
||||||
labels.isEmpty && milestoneId.isEmpty && author.isEmpty && assigned.isEmpty &&
|
labels.isEmpty && milestoneId.isEmpty && author.isEmpty && assigned.isEmpty &&
|
||||||
state == "open" && sort == "created" && direction == "desc"
|
state == "open" && sort == "created" && direction == "desc" && visibility.isEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
def nonEmpty: Boolean = !isEmpty
|
def nonEmpty: Boolean = !isEmpty
|
||||||
|
|
||||||
|
def toFilterString: String = (
|
||||||
|
List(
|
||||||
|
Some(s"is:${state}"),
|
||||||
|
author.map(author => s"author:${author}"),
|
||||||
|
assigned.map(assignee => s"assignee:${assignee}"),
|
||||||
|
mentioned.map(mentioned => s"mentions:${mentioned}")
|
||||||
|
).flatten ++
|
||||||
|
labels.map(label => s"label:${label}") ++
|
||||||
|
List(
|
||||||
|
milestoneId.map { _ match {
|
||||||
|
case Some(x) => s"milestone:${milestoneId}"
|
||||||
|
case None => "no:milestone"
|
||||||
|
}},
|
||||||
|
(sort, direction) match {
|
||||||
|
case ("created" , "desc") => None
|
||||||
|
case ("created" , "asc" ) => Some("sort:created-asc")
|
||||||
|
case ("comments", "desc") => Some("sort:comments-desc")
|
||||||
|
case ("comments", "asc" ) => Some("sort:comments-asc")
|
||||||
|
case ("updated" , "desc") => Some("sort:updated-desc")
|
||||||
|
case ("updated" , "asc" ) => Some("sort:updated-asc")
|
||||||
|
},
|
||||||
|
visibility.map(visibility => s"visibility:${visibility}")
|
||||||
|
).flatten ++
|
||||||
|
groups.map(group => s"group:${group}")
|
||||||
|
).mkString(" ")
|
||||||
|
|
||||||
def toURL: String =
|
def toURL: String =
|
||||||
"?" + List(
|
"?" + List(
|
||||||
if(labels.isEmpty) None else Some("labels=" + urlEncode(labels.mkString(","))),
|
if(labels.isEmpty) None else Some("labels=" + urlEncode(labels.mkString(","))),
|
||||||
milestoneId.map { id => "milestone=" + (id match {
|
milestoneId.map { _ match {
|
||||||
case Some(x) => x.toString
|
case Some(x) => "milestone=" + x
|
||||||
case None => "none"
|
case None => "milestone=none"
|
||||||
})},
|
}},
|
||||||
author .map(x => "author=" + urlEncode(x)),
|
author .map(x => "author=" + urlEncode(x)),
|
||||||
assigned.map(x => "assigned=" + urlEncode(x)),
|
assigned .map(x => "assigned=" + urlEncode(x)),
|
||||||
repo.map("for=" + urlEncode(_)),
|
mentioned.map(x => "mentioned=" + urlEncode(x)),
|
||||||
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)),
|
||||||
|
visibility.map(x => "visibility=" + urlEncode(x)),
|
||||||
|
if(groups.isEmpty) None else Some("groups=" + urlEncode(groups.mkString(",")))
|
||||||
|
).flatten.mkString("&")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -375,19 +389,63 @@ object IssuesService {
|
|||||||
if(value == null || value.isEmpty || (allow.nonEmpty && !allow.contains(value))) None else Some(value)
|
if(value == null || value.isEmpty || (allow.nonEmpty && !allow.contains(value))) None else Some(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restores IssueSearchCondition instance from filter query.
|
||||||
|
*/
|
||||||
|
def apply(filter: String, milestones: Map[String, Int]): IssueSearchCondition = {
|
||||||
|
val conditions = filter.split("[ \t]+").map { x =>
|
||||||
|
val dim = x.split(":")
|
||||||
|
dim(0) -> dim(1)
|
||||||
|
}.groupBy(_._1).map { case (key, values) =>
|
||||||
|
key -> values.map(_._2).toSeq
|
||||||
|
}
|
||||||
|
|
||||||
|
val (sort, direction) = conditions.get("sort").flatMap(_.headOption).getOrElse("created-desc") match {
|
||||||
|
case "created-asc" => ("created" , "asc" )
|
||||||
|
case "comments-desc" => ("comments", "desc")
|
||||||
|
case "comments-asc" => ("comments", "asc" )
|
||||||
|
case "updated-desc" => ("comments", "desc")
|
||||||
|
case "updated-asc" => ("comments", "asc" )
|
||||||
|
case _ => ("created" , "desc")
|
||||||
|
}
|
||||||
|
|
||||||
|
IssueSearchCondition(
|
||||||
|
conditions.get("label").map(_.toSet).getOrElse(Set.empty),
|
||||||
|
conditions.get("milestone").flatMap(_.headOption) match {
|
||||||
|
case None => None
|
||||||
|
case Some("none") => Some(None)
|
||||||
|
case Some(x) => milestones.get(x).map(x => Some(x))
|
||||||
|
},
|
||||||
|
conditions.get("author").flatMap(_.headOption),
|
||||||
|
conditions.get("assignee").flatMap(_.headOption),
|
||||||
|
conditions.get("mentions").flatMap(_.headOption),
|
||||||
|
conditions.get("is").getOrElse(Seq.empty).filter(x => x == "open" || x == "closed").headOption.getOrElse("open"),
|
||||||
|
sort,
|
||||||
|
direction,
|
||||||
|
conditions.get("visibility").flatMap(_.headOption),
|
||||||
|
conditions.get("group").map(_.toSet).getOrElse(Set.empty)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restores IssueSearchCondition instance from request parameters.
|
||||||
|
*/
|
||||||
def apply(request: HttpServletRequest): IssueSearchCondition =
|
def apply(request: HttpServletRequest): IssueSearchCondition =
|
||||||
IssueSearchCondition(
|
IssueSearchCondition(
|
||||||
param(request, "labels").map(_.split(",").toSet).getOrElse(Set.empty),
|
param(request, "labels").map(_.split(",").toSet).getOrElse(Set.empty),
|
||||||
param(request, "milestone").map{
|
param(request, "milestone").map {
|
||||||
case "none" => None
|
case "none" => None
|
||||||
case x => x.toIntOpt
|
case x => x.toIntOpt
|
||||||
},
|
},
|
||||||
param(request, "author"),
|
param(request, "author"),
|
||||||
param(request, "assigned"),
|
param(request, "assigned"),
|
||||||
param(request, "for"),
|
param(request, "mentioned"),
|
||||||
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"),
|
||||||
|
param(request, "visibility"),
|
||||||
|
param(request, "groups").map(_.split(",").toSet).getOrElse(Set.empty)
|
||||||
|
)
|
||||||
|
|
||||||
def page(request: HttpServletRequest) = try {
|
def page(request: HttpServletRequest) = try {
|
||||||
val i = param(request, "page").getOrElse("1").toInt
|
val i = param(request, "page").getOrElse("1").toInt
|
||||||
|
|||||||
@@ -36,23 +36,23 @@ trait PullRequestService { self: IssuesService =>
|
|||||||
.list
|
.list
|
||||||
.map { x => PullRequestCount(x._1, x._2) }
|
.map { x => PullRequestCount(x._1, x._2) }
|
||||||
|
|
||||||
def getAllPullRequestCountGroupByUser(closed: Boolean, userName: String)(implicit s: Session): List[PullRequestCount] =
|
// def getAllPullRequestCountGroupByUser(closed: Boolean, userName: String)(implicit s: Session): List[PullRequestCount] =
|
||||||
PullRequests
|
// PullRequests
|
||||||
.innerJoin(Issues).on { (t1, t2) => t1.byPrimaryKey(t2.userName, t2.repositoryName, t2.issueId) }
|
// .innerJoin(Issues).on { (t1, t2) => t1.byPrimaryKey(t2.userName, t2.repositoryName, t2.issueId) }
|
||||||
.innerJoin(Repositories).on { case ((t1, t2), t3) => t2.byRepository(t3.userName, t3.repositoryName) }
|
// .innerJoin(Repositories).on { case ((t1, t2), t3) => t2.byRepository(t3.userName, t3.repositoryName) }
|
||||||
.filter { case ((t1, t2), t3) =>
|
// .filter { case ((t1, t2), t3) =>
|
||||||
(t2.closed === closed.bind) &&
|
// (t2.closed === closed.bind) &&
|
||||||
(
|
// (
|
||||||
(t3.isPrivate === false.bind) ||
|
// (t3.isPrivate === false.bind) ||
|
||||||
(t3.userName === userName.bind) ||
|
// (t3.userName === userName.bind) ||
|
||||||
(Collaborators.filter { t4 => t4.byRepository(t3.userName, t3.repositoryName) && (t4.collaboratorName === userName.bind)} exists)
|
// (Collaborators.filter { t4 => t4.byRepository(t3.userName, t3.repositoryName) && (t4.collaboratorName === userName.bind)} exists)
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
.groupBy { case ((t1, t2), t3) => t2.openedUserName }
|
// .groupBy { case ((t1, t2), t3) => t2.openedUserName }
|
||||||
.map { case (userName, t) => userName -> t.length }
|
// .map { case (userName, t) => userName -> t.length }
|
||||||
.sortBy(_._2 desc)
|
// .sortBy(_._2 desc)
|
||||||
.list
|
// .list
|
||||||
.map { x => PullRequestCount(x._1, x._2) }
|
// .map { x => PullRequestCount(x._1, x._2) }
|
||||||
|
|
||||||
def createPullRequest(originUserName: String, originRepositoryName: String, issueId: Int,
|
def createPullRequest(originUserName: String, originRepositoryName: String, issueId: Int,
|
||||||
originBranch: String, requestUserName: String, requestRepositoryName: String, requestBranch: String,
|
originBranch: String, requestUserName: String, requestRepositoryName: String, requestBranch: String,
|
||||||
|
|||||||
@@ -54,7 +54,6 @@ trait RepositoryService { self: AccountService =>
|
|||||||
val labels = Labels .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
val labels = Labels .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||||
val issueComments = IssueComments.filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
val issueComments = IssueComments.filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||||
val issueLabels = IssueLabels .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
val issueLabels = IssueLabels .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||||
val activities = Activities .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
|
||||||
val collaborators = Collaborators.filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
val collaborators = Collaborators.filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||||
|
|
||||||
Repositories.filter { t =>
|
Repositories.filter { t =>
|
||||||
@@ -69,10 +68,17 @@ trait RepositoryService { self: AccountService =>
|
|||||||
t.requestRepositoryName === oldRepositoryName.bind
|
t.requestRepositoryName === oldRepositoryName.bind
|
||||||
}.map { t => t.requestUserName -> t.requestRepositoryName }.update(newUserName, newRepositoryName)
|
}.map { t => t.requestUserName -> t.requestRepositoryName }.update(newUserName, newRepositoryName)
|
||||||
|
|
||||||
|
// Updates activity fk before deleting repository because activity is sorted by activityId
|
||||||
|
// and it can't be changed by deleting-and-inserting record.
|
||||||
|
Activities.filter(_.byRepository(oldUserName, oldRepositoryName)).list.foreach { activity =>
|
||||||
|
Activities.filter(_.activityId === activity.activityId.bind)
|
||||||
|
.map(x => (x.userName, x.repositoryName)).update(newUserName, newRepositoryName)
|
||||||
|
}
|
||||||
|
|
||||||
deleteRepository(oldUserName, oldRepositoryName)
|
deleteRepository(oldUserName, oldRepositoryName)
|
||||||
|
|
||||||
WebHooks .insertAll(webHooks .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
WebHooks .insertAll(webHooks .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
||||||
Milestones .insertAll(milestones .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
Milestones.insertAll(milestones .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
||||||
IssueId .insertAll(issueId .map(_.copy(_1 = newUserName, _2 = newRepositoryName)) :_*)
|
IssueId .insertAll(issueId .map(_.copy(_1 = newUserName, _2 = newRepositoryName)) :_*)
|
||||||
|
|
||||||
val newMilestones = Milestones.filter(_.byRepository(newUserName, newRepositoryName)).list
|
val newMilestones = Milestones.filter(_.byRepository(newUserName, newRepositoryName)).list
|
||||||
@@ -88,7 +94,7 @@ trait RepositoryService { self: AccountService =>
|
|||||||
IssueComments .insertAll(issueComments .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
IssueComments .insertAll(issueComments .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
||||||
Labels .insertAll(labels .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
Labels .insertAll(labels .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
||||||
IssueLabels .insertAll(issueLabels .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
IssueLabels .insertAll(issueLabels .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
||||||
Activities .insertAll(activities .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
|
||||||
if(account.isGroupAccount){
|
if(account.isGroupAccount){
|
||||||
Collaborators.insertAll(getGroupMembers(newUserName).map(m => Collaborator(newUserName, newRepositoryName, m.userName)) :_*)
|
Collaborators.insertAll(getGroupMembers(newUserName).map(m => Collaborator(newUserName, newRepositoryName, m.userName)) :_*)
|
||||||
} else {
|
} else {
|
||||||
@@ -96,12 +102,9 @@ trait RepositoryService { self: AccountService =>
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update activity messages
|
// Update activity messages
|
||||||
val updateActivities = Activities.filter { t =>
|
Activities.filter { t =>
|
||||||
(t.message like s"%:${oldUserName}/${oldRepositoryName}]%") ||
|
(t.message like s"%:${oldUserName}/${oldRepositoryName}]%") || (t.message like s"%:${oldUserName}/${oldRepositoryName}#%")
|
||||||
(t.message like s"%:${oldUserName}/${oldRepositoryName}#%")
|
}.map { t => t.activityId -> t.message }.list.foreach { case (activityId, message) =>
|
||||||
}.map { t => t.activityId -> t.message }.list
|
|
||||||
|
|
||||||
updateActivities.foreach { case (activityId, message) =>
|
|
||||||
Activities.filter(_.activityId === activityId.bind).map(_.message).update(
|
Activities.filter(_.activityId === activityId.bind).map(_.message).update(
|
||||||
message
|
message
|
||||||
.replace(s"[repo:${oldUserName}/${oldRepositoryName}]" ,s"[repo:${newUserName}/${newRepositoryName}]")
|
.replace(s"[repo:${oldUserName}/${oldRepositoryName}]" ,s"[repo:${newUserName}/${newRepositoryName}]")
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ trait SystemSettingsService {
|
|||||||
def saveSystemSettings(settings: SystemSettings): Unit = {
|
def saveSystemSettings(settings: SystemSettings): Unit = {
|
||||||
defining(new java.util.Properties()){ props =>
|
defining(new java.util.Properties()){ props =>
|
||||||
settings.baseUrl.foreach(x => props.setProperty(BaseURL, x.replaceFirst("/\\Z", "")))
|
settings.baseUrl.foreach(x => props.setProperty(BaseURL, x.replaceFirst("/\\Z", "")))
|
||||||
|
settings.information.foreach(x => props.setProperty(Information, x))
|
||||||
props.setProperty(AllowAccountRegistration, settings.allowAccountRegistration.toString)
|
props.setProperty(AllowAccountRegistration, settings.allowAccountRegistration.toString)
|
||||||
props.setProperty(Gravatar, settings.gravatar.toString)
|
props.setProperty(Gravatar, settings.gravatar.toString)
|
||||||
props.setProperty(Notification, settings.notification.toString)
|
props.setProperty(Notification, settings.notification.toString)
|
||||||
@@ -60,6 +61,7 @@ trait SystemSettingsService {
|
|||||||
}
|
}
|
||||||
SystemSettings(
|
SystemSettings(
|
||||||
getOptionValue[String](props, BaseURL, None).map(x => x.replaceFirst("/\\Z", "")),
|
getOptionValue[String](props, BaseURL, None).map(x => x.replaceFirst("/\\Z", "")),
|
||||||
|
getOptionValue[String](props, Information, None),
|
||||||
getValue(props, AllowAccountRegistration, false),
|
getValue(props, AllowAccountRegistration, false),
|
||||||
getValue(props, Gravatar, true),
|
getValue(props, Gravatar, true),
|
||||||
getValue(props, Notification, false),
|
getValue(props, Notification, false),
|
||||||
@@ -105,6 +107,7 @@ object SystemSettingsService {
|
|||||||
|
|
||||||
case class SystemSettings(
|
case class SystemSettings(
|
||||||
baseUrl: Option[String],
|
baseUrl: Option[String],
|
||||||
|
information: Option[String],
|
||||||
allowAccountRegistration: Boolean,
|
allowAccountRegistration: Boolean,
|
||||||
gravatar: Boolean,
|
gravatar: Boolean,
|
||||||
notification: Boolean,
|
notification: Boolean,
|
||||||
@@ -147,6 +150,7 @@ object SystemSettingsService {
|
|||||||
val DefaultLdapPort = 389
|
val DefaultLdapPort = 389
|
||||||
|
|
||||||
private val BaseURL = "base_url"
|
private val BaseURL = "base_url"
|
||||||
|
private val Information = "information"
|
||||||
private val AllowAccountRegistration = "allow_account_registration"
|
private val AllowAccountRegistration = "allow_account_registration"
|
||||||
private val Gravatar = "gravatar"
|
private val Gravatar = "gravatar"
|
||||||
private val Notification = "notification"
|
private val Notification = "notification"
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ object AutoUpdate {
|
|||||||
* The history of versions. A head of this sequence is the current BitBucket version.
|
* The history of versions. A head of this sequence is the current BitBucket version.
|
||||||
*/
|
*/
|
||||||
val versions = Seq(
|
val versions = Seq(
|
||||||
|
new Version(2, 5),
|
||||||
new Version(2, 4),
|
new Version(2, 4),
|
||||||
new Version(2, 3) {
|
new Version(2, 3) {
|
||||||
override def update(conn: Connection): Unit = {
|
override def update(conn: Connection): Unit = {
|
||||||
|
|||||||
@@ -134,8 +134,8 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
|||||||
|
|
||||||
// Retrieve all issue count in the repository
|
// Retrieve all issue count in the repository
|
||||||
val issueCount =
|
val issueCount =
|
||||||
countIssue(IssueSearchCondition(state = "open"), Map.empty, false, owner -> repository) +
|
countIssue(IssueSearchCondition(state = "open"), false, owner -> repository) +
|
||||||
countIssue(IssueSearchCondition(state = "closed"), Map.empty, false, owner -> repository)
|
countIssue(IssueSearchCondition(state = "closed"), false, owner -> repository)
|
||||||
|
|
||||||
// Extract new commit and apply issue comment
|
// Extract new commit and apply issue comment
|
||||||
val defaultBranch = getRepository(owner, repository, baseUrl).get.repository.defaultBranch
|
val defaultBranch = getRepository(owner, repository, baseUrl).get.repository.defaultBranch
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import org.eclipse.jgit.treewalk.filter._
|
|||||||
import org.eclipse.jgit.diff.DiffEntry.ChangeType
|
import org.eclipse.jgit.diff.DiffEntry.ChangeType
|
||||||
import org.eclipse.jgit.errors.{ConfigInvalidException, MissingObjectException}
|
import org.eclipse.jgit.errors.{ConfigInvalidException, MissingObjectException}
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import org.eclipse.jgit.api.errors.NoHeadException
|
import org.eclipse.jgit.api.errors.{JGitInternalException, InvalidRefNameException, RefAlreadyExistsException, NoHeadException}
|
||||||
import service.RepositoryService
|
import service.RepositoryService
|
||||||
import org.eclipse.jgit.dircache.DirCacheEntry
|
import org.eclipse.jgit.dircache.DirCacheEntry
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
@@ -507,6 +507,17 @@ object JGitUtil {
|
|||||||
}.find(_._1 != null)
|
}.find(_._1 != null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def createBranch(git: Git, fromBranch: String, newBranch: String) = {
|
||||||
|
try {
|
||||||
|
git.branchCreate().setStartPoint(fromBranch).setName(newBranch).call()
|
||||||
|
Right("Branch created.")
|
||||||
|
} catch {
|
||||||
|
case e: RefAlreadyExistsException => Left("Sorry, that branch already exists.")
|
||||||
|
// JGitInternalException occurs when new branch name is 'a' and the branch whose name is 'a/*' exists.
|
||||||
|
case _: InvalidRefNameException | _: JGitInternalException => Left("Sorry, that name is invalid.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def createDirCacheEntry(path: String, mode: FileMode, objectId: ObjectId): DirCacheEntry = {
|
def createDirCacheEntry(path: String, mode: FileMode, objectId: ObjectId): DirCacheEntry = {
|
||||||
val entry = new DirCacheEntry(path)
|
val entry = new DirCacheEntry(path)
|
||||||
entry.setFileMode(mode)
|
entry.setFileMode(mode)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import org.pegdown.ast._
|
|||||||
import org.pegdown.LinkRenderer.Rendering
|
import org.pegdown.LinkRenderer.Rendering
|
||||||
import java.text.Normalizer
|
import java.text.Normalizer
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
import java.util.regex.Pattern
|
||||||
import scala.collection.JavaConverters._
|
import scala.collection.JavaConverters._
|
||||||
import service.{RequestCache, WikiService}
|
import service.{RequestCache, WikiService}
|
||||||
|
|
||||||
@@ -18,17 +19,23 @@ object Markdown {
|
|||||||
* Converts Markdown of Wiki pages to HTML.
|
* Converts Markdown of Wiki pages to HTML.
|
||||||
*/
|
*/
|
||||||
def toHtml(markdown: String, repository: service.RepositoryService.RepositoryInfo,
|
def toHtml(markdown: String, repository: service.RepositoryService.RepositoryInfo,
|
||||||
enableWikiLink: Boolean, enableRefsLink: Boolean)(implicit context: app.Context): String = {
|
enableWikiLink: Boolean, enableRefsLink: Boolean,
|
||||||
|
enableTaskList: Boolean = false, hasWritePermission: Boolean = false)(implicit context: app.Context): String = {
|
||||||
// escape issue id
|
// escape issue id
|
||||||
val source = if(enableRefsLink){
|
val s = if(enableRefsLink){
|
||||||
markdown.replaceAll("(?<=(\\W|^))#(\\d+)(?=(\\W|$))", "issue:$2")
|
markdown.replaceAll("(?<=(\\W|^))#(\\d+)(?=(\\W|$))", "issue:$2")
|
||||||
} else markdown
|
} else markdown
|
||||||
|
|
||||||
|
// escape task list
|
||||||
|
val source = if(enableTaskList){
|
||||||
|
GitBucketHtmlSerializer.escapeTaskList(s)
|
||||||
|
} else s
|
||||||
|
|
||||||
val rootNode = new PegDownProcessor(
|
val rootNode = new PegDownProcessor(
|
||||||
Extensions.AUTOLINKS | Extensions.WIKILINKS | Extensions.FENCED_CODE_BLOCKS | Extensions.TABLES | Extensions.HARDWRAPS
|
Extensions.AUTOLINKS | Extensions.WIKILINKS | Extensions.FENCED_CODE_BLOCKS | Extensions.TABLES | Extensions.HARDWRAPS
|
||||||
).parseMarkdown(source.toCharArray)
|
).parseMarkdown(source.toCharArray)
|
||||||
|
|
||||||
new GitBucketHtmlSerializer(markdown, repository, enableWikiLink, enableRefsLink).toHtml(rootNode)
|
new GitBucketHtmlSerializer(markdown, repository, enableWikiLink, enableRefsLink, enableTaskList, hasWritePermission).toHtml(rootNode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,7 +89,9 @@ class GitBucketHtmlSerializer(
|
|||||||
markdown: String,
|
markdown: String,
|
||||||
repository: service.RepositoryService.RepositoryInfo,
|
repository: service.RepositoryService.RepositoryInfo,
|
||||||
enableWikiLink: Boolean,
|
enableWikiLink: Boolean,
|
||||||
enableRefsLink: Boolean
|
enableRefsLink: Boolean,
|
||||||
|
enableTaskList: Boolean,
|
||||||
|
hasWritePermission: Boolean
|
||||||
)(implicit val context: app.Context) extends ToHtmlSerializer(
|
)(implicit val context: app.Context) extends ToHtmlSerializer(
|
||||||
new GitBucketLinkRender(context, repository, enableWikiLink),
|
new GitBucketLinkRender(context, repository, enableWikiLink),
|
||||||
Map[String, VerbatimSerializer](VerbatimSerializer.DEFAULT -> new GitBucketVerbatimSerializer).asJava
|
Map[String, VerbatimSerializer](VerbatimSerializer.DEFAULT -> new GitBucketVerbatimSerializer).asJava
|
||||||
@@ -143,7 +152,10 @@ class GitBucketHtmlSerializer(
|
|||||||
|
|
||||||
override def visit(node: TextNode): Unit = {
|
override def visit(node: TextNode): Unit = {
|
||||||
// convert commit id and username to link.
|
// convert commit id and username to link.
|
||||||
val text = if(enableRefsLink) convertRefsLinks(node.getText, repository, "issue:") else node.getText
|
val t = if(enableRefsLink) convertRefsLinks(node.getText, repository, "issue:") else node.getText
|
||||||
|
|
||||||
|
// convert task list to checkbox.
|
||||||
|
val text = if(enableTaskList) GitBucketHtmlSerializer.convertCheckBox(t, hasWritePermission) else t
|
||||||
|
|
||||||
if (abbreviations.isEmpty) {
|
if (abbreviations.isEmpty) {
|
||||||
printer.print(text)
|
printer.print(text)
|
||||||
@@ -151,6 +163,28 @@ class GitBucketHtmlSerializer(
|
|||||||
printWithAbbreviations(text)
|
printWithAbbreviations(text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override def visit(node: BulletListNode): Unit = {
|
||||||
|
if (printChildrenToString(node).contains("""class="task-list-item-checkbox" """)) {
|
||||||
|
printer.println().print("""<ul class="task-list">""").indent(+2)
|
||||||
|
visitChildren(node)
|
||||||
|
printer.indent(-2).println().print("</ul>")
|
||||||
|
} else {
|
||||||
|
printIndentedTag(node, "ul")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override def visit(node: ListItemNode): Unit = {
|
||||||
|
if (printChildrenToString(node).contains("""class="task-list-item-checkbox" """)) {
|
||||||
|
printer.println()
|
||||||
|
printer.print("""<li class="task-list-item">""")
|
||||||
|
visitChildren(node)
|
||||||
|
printer.print("</li>")
|
||||||
|
} else {
|
||||||
|
printer.println()
|
||||||
|
printTag(node, "li")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object GitBucketHtmlSerializer {
|
object GitBucketHtmlSerializer {
|
||||||
@@ -163,4 +197,14 @@ object GitBucketHtmlSerializer {
|
|||||||
val noSpecialChars = StringUtil.urlEncode(normalized)
|
val noSpecialChars = StringUtil.urlEncode(normalized)
|
||||||
noSpecialChars.toLowerCase(Locale.ENGLISH)
|
noSpecialChars.toLowerCase(Locale.ENGLISH)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def escapeTaskList(text: String): String = {
|
||||||
|
Pattern.compile("""^( *)- \[([x| ])\] """, Pattern.MULTILINE).matcher(text).replaceAll("$1* task:$2: ")
|
||||||
|
}
|
||||||
|
|
||||||
|
def convertCheckBox(text: String, hasWritePermission: Boolean): String = {
|
||||||
|
val disabled = if (hasWritePermission) "" else "disabled"
|
||||||
|
text.replaceAll("task:x:", """<input type="checkbox" class="task-list-item-checkbox" checked="checked" """ + disabled + "/>")
|
||||||
|
.replaceAll("task: :", """<input type="checkbox" class="task-list-item-checkbox" """ + disabled + "/>")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
package view
|
package view
|
||||||
import java.util.{Date, TimeZone}
|
import java.util.{Locale, Date, TimeZone}
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import play.twirl.api.Html
|
import play.twirl.api.Html
|
||||||
import util.StringUtil
|
import util.StringUtil
|
||||||
@@ -15,6 +15,47 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
|
|||||||
*/
|
*/
|
||||||
def datetime(date: Date): String = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date)
|
def datetime(date: Date): String = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date)
|
||||||
|
|
||||||
|
val timeUnits = List(
|
||||||
|
(1000L, "second"),
|
||||||
|
(1000L * 60, "minute"),
|
||||||
|
(1000L * 60 * 60, "hour"),
|
||||||
|
(1000L * 60 * 60 * 24, "day"),
|
||||||
|
(1000L * 60 * 60 * 24 * 30, "month"),
|
||||||
|
(1000L * 60 * 60 * 24 * 365, "year")
|
||||||
|
).reverse
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format java.util.Date to "x {seconds/minutes/hours/days/months/years} ago"
|
||||||
|
*/
|
||||||
|
def datetimeAgo(date: Date): String = {
|
||||||
|
val duration = new Date().getTime - date.getTime
|
||||||
|
timeUnits.find(tuple => duration / tuple._1 > 0) match {
|
||||||
|
case Some((unitValue, unitString)) =>
|
||||||
|
val value = duration / unitValue
|
||||||
|
s"${value} ${unitString}${if (value > 1) "s" else ""} ago"
|
||||||
|
case None => "just now"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Format java.util.Date to "x {seconds/minutes/hours/days} ago"
|
||||||
|
* If duration over 1 month, format to "d MMM (yyyy)"
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
def datetimeAgoRecentOnly(date: Date): String = {
|
||||||
|
val duration = new Date().getTime - date.getTime
|
||||||
|
timeUnits.find(tuple => duration / tuple._1 > 0) match {
|
||||||
|
case Some((_, "month")) => s"on ${new SimpleDateFormat("d MMM", Locale.ENGLISH).format(date)}"
|
||||||
|
case Some((_, "year")) => s"on ${new SimpleDateFormat("d MMM yyyy", Locale.ENGLISH).format(date)}"
|
||||||
|
case Some((unitValue, unitString)) =>
|
||||||
|
val value = duration / unitValue
|
||||||
|
s"${value} ${unitString}${if (value > 1) "s" else ""} ago"
|
||||||
|
case None => "just now"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Format java.util.Date to "yyyy-MM-dd'T'hh:mm:ss'Z'".
|
* Format java.util.Date to "yyyy-MM-dd'T'hh:mm:ss'Z'".
|
||||||
*/
|
*/
|
||||||
@@ -48,8 +89,8 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
|
|||||||
* Converts Markdown of Wiki pages to HTML.
|
* Converts Markdown of Wiki pages to HTML.
|
||||||
*/
|
*/
|
||||||
def markdown(value: String, repository: service.RepositoryService.RepositoryInfo,
|
def markdown(value: String, repository: service.RepositoryService.RepositoryInfo,
|
||||||
enableWikiLink: Boolean, enableRefsLink: Boolean)(implicit context: app.Context): Html =
|
enableWikiLink: Boolean, enableRefsLink: Boolean, enableTaskList: Boolean = false, hasWritePermission: Boolean = false)(implicit context: app.Context): Html =
|
||||||
Html(Markdown.toHtml(value, repository, enableWikiLink, enableRefsLink))
|
Html(Markdown.toHtml(value, repository, enableWikiLink, enableRefsLink, enableTaskList, hasWritePermission))
|
||||||
|
|
||||||
def renderMarkup(filePath: List[String], fileContent: String, branch: String,
|
def renderMarkup(filePath: List[String], fileContent: String, branch: String,
|
||||||
repository: service.RepositoryService.RepositoryInfo,
|
repository: service.RepositoryService.RepositoryInfo,
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
@if(repository.repository.description.isDefined){
|
@if(repository.repository.description.isDefined){
|
||||||
<div>@repository.repository.description</div>
|
<div>@repository.repository.description</div>
|
||||||
}
|
}
|
||||||
<div><span class="muted small">Last updated: @datetime(repository.repository.lastActivityDate)</span></div>
|
<div><span class="muted small">Updated @helper.html.datetimeago(repository.repository.lastActivityDate)</span></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,14 @@
|
|||||||
You can use this property to adjust URL difference between the reverse proxy and GitBucket.
|
You can use this property to adjust URL difference between the reverse proxy and GitBucket.
|
||||||
</p>
|
</p>
|
||||||
<!--====================================================================-->
|
<!--====================================================================-->
|
||||||
|
<!-- Information -->
|
||||||
|
<!--====================================================================-->
|
||||||
|
<hr>
|
||||||
|
<label><span class="strong">Information</span> (HTML is available)</label>
|
||||||
|
<fieldset>
|
||||||
|
<textarea name="information" style="width: 600px; height: 100px;">@settings.information</textarea>
|
||||||
|
</fieldset>
|
||||||
|
<!--====================================================================-->
|
||||||
<!-- Account registration -->
|
<!-- Account registration -->
|
||||||
<!--====================================================================-->
|
<!--====================================================================-->
|
||||||
<hr>
|
<hr>
|
||||||
|
|||||||
@@ -16,6 +16,9 @@
|
|||||||
<input type="checkbox" name="removed" id="removed" value="true" @if(account.get.isRemoved){checked}/>
|
<input type="checkbox" name="removed" id="removed" value="true" @if(account.get.isRemoved){checked}/>
|
||||||
Disable
|
Disable
|
||||||
</label>
|
</label>
|
||||||
|
<div>
|
||||||
|
<span id="error-removed" class="error"></span>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
@if(account.map(_.password.nonEmpty).getOrElse(true)){
|
@if(account.map(_.password.nonEmpty).getOrElse(true)){
|
||||||
|
|||||||
74
src/main/twirl/dashboard/header.scala.html
Normal file
74
src/main/twirl/dashboard/header.scala.html
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
@(openCount: Int,
|
||||||
|
closedCount: Int,
|
||||||
|
condition: service.IssuesService.IssueSearchCondition,
|
||||||
|
groups: List[String])(implicit context: app.Context)
|
||||||
|
@import context._
|
||||||
|
@import view.helpers._
|
||||||
|
<span class="small">
|
||||||
|
<a class="button-link@if(condition.state == "open"){ selected}" href="@condition.copy(state = "open").toURL">
|
||||||
|
<img src="@assets/common/images/status-open@(if(condition.state == "open"){"-active"}).png"/>
|
||||||
|
@openCount Open
|
||||||
|
</a>
|
||||||
|
<a class="button-link@if(condition.state == "closed"){ selected}" href="@condition.copy(state = "closed").toURL">
|
||||||
|
<img src="@assets/common/images/status-closed@(if(condition.state == "closed"){"-active"}).png"/>
|
||||||
|
@closedCount Closed
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
<div class="pull-right" id="table-issues-control">
|
||||||
|
@helper.html.dropdown("Visibility", flat = true){
|
||||||
|
<li>
|
||||||
|
<a href="@(condition.copy(visibility = (if(condition.visibility == Some("private")) None else Some("private"))).toURL)">
|
||||||
|
@helper.html.checkicon(condition.visibility == Some("private"))
|
||||||
|
Private repository only
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="@(condition.copy(visibility = (if(condition.visibility == Some("public")) None else Some("public"))).toURL)">
|
||||||
|
@helper.html.checkicon(condition.visibility == Some("public"))
|
||||||
|
Public repository only
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
@helper.html.dropdown("Organization", flat = true){
|
||||||
|
@groups.map { group =>
|
||||||
|
<li>
|
||||||
|
<a href="@((if(condition.groups.contains(group)) condition.copy(groups = condition.groups - group) else condition.copy(groups = condition.groups + group)).toURL)">
|
||||||
|
@helper.html.checkicon(condition.groups.contains(group))
|
||||||
|
@avatar(group, 20) @group
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@helper.html.dropdown("Sort", flat = true){
|
||||||
|
<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>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
@@ -1,50 +1,16 @@
|
|||||||
@(listparts: play.twirl.api.Html,
|
@(issues: List[service.IssuesService.IssueInfo],
|
||||||
allCount: Int,
|
page: Int,
|
||||||
assignedCount: Int,
|
openCount: Int,
|
||||||
createdByCount: Int,
|
closedCount: Int,
|
||||||
repositories: List[(String, String, Int)],
|
|
||||||
condition: service.IssuesService.IssueSearchCondition,
|
condition: service.IssuesService.IssueSearchCondition,
|
||||||
filter: String)(implicit context: app.Context)
|
filter: String,
|
||||||
|
groups: List[String])(implicit context: app.Context)
|
||||||
@import context._
|
@import context._
|
||||||
@import view.helpers._
|
@import view.helpers._
|
||||||
@html.main("Your Issues"){
|
@html.main("Issues"){
|
||||||
<div class="container">
|
|
||||||
@dashboard.html.tab("issues")
|
@dashboard.html.tab("issues")
|
||||||
<div class="row-fluid">
|
<div class="container">
|
||||||
<div class="span3">
|
@issuesnavi(filter, "issues", condition)
|
||||||
<ul class="nav nav-pills nav-stacked">
|
@issueslist(issues, page, openCount, closedCount, condition, filter, groups)
|
||||||
<li@if(filter == "all"){ class="active"}>
|
|
||||||
<a href="@path/dashboard/issues/repos@condition.toURL">
|
|
||||||
<span class="count-right">@allCount</span>
|
|
||||||
In your repositories
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li@if(filter == "assigned"){ class="active"}>
|
|
||||||
<a href="@path/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="@path/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 { case (owner, name, count) =>
|
|
||||||
<li@if(condition.repo == Some(owner + "/" + name)){ class="active"}>
|
|
||||||
<a href="@condition.copy(repo = Some(owner + "/" + name)).toURL">
|
|
||||||
<span class="count-right">@count</span>
|
|
||||||
@owner/@name
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
@listparts
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,153 +3,33 @@
|
|||||||
openCount: Int,
|
openCount: Int,
|
||||||
closedCount: Int,
|
closedCount: Int,
|
||||||
condition: service.IssuesService.IssueSearchCondition,
|
condition: service.IssuesService.IssueSearchCondition,
|
||||||
collaborators: List[String] = Nil,
|
filter: String,
|
||||||
milestones: List[model.Milestone] = Nil,
|
groups: List[String])(implicit context: app.Context)
|
||||||
labels: List[model.Label] = Nil,
|
|
||||||
repository: Option[service.RepositoryService.RepositoryInfo] = None,
|
|
||||||
hasWritePermission: Boolean = false)(implicit context: app.Context)
|
|
||||||
@import context._
|
@import context._
|
||||||
@import view.helpers._
|
@import view.helpers._
|
||||||
@import service.IssuesService.IssueInfo
|
@import service.IssuesService.IssueInfo
|
||||||
<div class="span9">
|
@*
|
||||||
@if(condition.labels.nonEmpty || condition.milestoneId.isDefined){
|
<ul class="nav nav-pills-group pull-left fill-width">
|
||||||
<a href="@condition.copy(labels = Set.empty, milestoneId = None).toURL" id="clear-filter">
|
<li class="@if(filter == "created_by"){active} first"><a href="@path/dashboard/issues/created_by@condition.toURL">Created</a></li>
|
||||||
<i class="icon-remove-circle"></i> Clear milestone and label filters
|
<li class="@if(filter == "assigned"){active}"><a href="@path/dashboard/issues/assigned@condition.toURL">Assigned</a></li>
|
||||||
</a>
|
<li class="@if(filter == "mentioned"){active} last"><a href="@path/dashboard/issues/mentioned@condition.toURL">Mentioned</a></li>
|
||||||
}
|
</ul>
|
||||||
@if(condition.repo.isDefined){
|
*@
|
||||||
<a href="@condition.copy(repo = None).toURL" id="clear-filter">
|
<table class="table table-bordered table-hover table-issues">
|
||||||
<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 btn-small@if(condition.state == "open"){ active}" href="@condition.copy(state = "open").toURL">@openCount Open</a>
|
|
||||||
<a class="btn btn-small@if(condition.state == "closed"){ active}" href="@condition.copy(state = "closed").toURL">@closedCount Closed</a>
|
|
||||||
</div>
|
|
||||||
@helper.html.dropdown(
|
|
||||||
value = (condition.sort, condition.direction) match {
|
|
||||||
case ("created" , "desc") => "Newest"
|
|
||||||
case ("created" , "asc" ) => "Oldest"
|
|
||||||
case ("comments", "desc") => "Most commented"
|
|
||||||
case ("comments", "asc" ) => "Least commented"
|
|
||||||
case ("updated" , "desc") => "Recently updated"
|
|
||||||
case ("updated" , "asc" ) => "Least recently updated"
|
|
||||||
},
|
|
||||||
prefix = "Sort",
|
|
||||||
mini = false
|
|
||||||
){
|
|
||||||
<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>
|
|
||||||
}
|
|
||||||
<table class="table table-bordered table-hover table-issues">
|
|
||||||
@if(issues.isEmpty){
|
|
||||||
<tr>
|
<tr>
|
||||||
<td style="padding: 20px; background-color: #eee; text-align: center;">
|
<th style="background-color: #eee;">
|
||||||
No issues to show.
|
@dashboard.html.header(openCount, closedCount, condition, groups)
|
||||||
@if(condition.labels.nonEmpty || condition.milestoneId.isDefined){
|
</th>
|
||||||
<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>
|
</tr>
|
||||||
} else {
|
|
||||||
@if(hasWritePermission){
|
|
||||||
<tr>
|
|
||||||
<td style="background-color: #eee;">
|
|
||||||
<div class="btn-group">
|
|
||||||
<button class="btn btn-mini strong" id="state">@{if(condition.state == "open") "Close" else "Reopen"}</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.png"/><span class="milestone-alert">Due by @date(dueDate)</span>
|
|
||||||
} else {
|
|
||||||
<span class="muted">Due by @date(dueDate)</span>
|
|
||||||
}
|
|
||||||
}.getOrElse {
|
|
||||||
<span class="muted">No due date</span>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@issues.map { case IssueInfo(issue, labels, milestone, commentCount) =>
|
@issues.map { case IssueInfo(issue, labels, milestone, commentCount) =>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td style="padding-top: 15px; padding-bottom: 15px;">
|
||||||
@if(hasWritePermission){
|
|
||||||
<label class="checkbox" style="cursor: default;">
|
|
||||||
<input type="checkbox" value="@issue.issueId"/>
|
|
||||||
}
|
|
||||||
@if(issue.isPullRequest){
|
@if(issue.isPullRequest){
|
||||||
<img src="@assets/common/images/pullreq-@(if(issue.closed) "closed" else "open").png"/>
|
<img src="@assets/common/images/pullreq-@(if(issue.closed) "closed" else "open").png"/>
|
||||||
} else {
|
} else {
|
||||||
<img src="@assets/common/images/issue-@(if(issue.closed) "closed" else "open").png"/>
|
<img src="@assets/common/images/issue-@(if(issue.closed) "closed" else "open").png"/>
|
||||||
}
|
}
|
||||||
@if(repository.isEmpty){
|
|
||||||
<a href="@path/@issue.userName/@issue.repositoryName">@issue.repositoryName</a> ・
|
<a href="@path/@issue.userName/@issue.repositoryName">@issue.repositoryName</a> ・
|
||||||
}
|
|
||||||
@if(issue.isPullRequest){
|
@if(issue.isPullRequest){
|
||||||
<a href="@path/@issue.userName/@issue.repositoryName/pull/@issue.issueId" class="issue-title">@issue.title</a>
|
<a href="@path/@issue.userName/@issue.repositoryName/pull/@issue.issueId" class="issue-title">@issue.title</a>
|
||||||
} else {
|
} else {
|
||||||
@@ -162,23 +42,26 @@
|
|||||||
@issue.assignedUserName.map { userName =>
|
@issue.assignedUserName.map { userName =>
|
||||||
@avatar(userName, 20, tooltip = true)
|
@avatar(userName, 20, tooltip = true)
|
||||||
}
|
}
|
||||||
#@issue.issueId
|
|
||||||
</span>
|
|
||||||
<div class="small muted" style="margin-left: 20px;">
|
|
||||||
Opened by @user(issue.openedUserName, styleClass="username") @datetime(issue.registeredDate)
|
|
||||||
@if(commentCount > 0){
|
@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>
|
<a href="@path/@issue.userName/@issue.repositoryName/issues/@issue.issueId" class="issue-comment-count">
|
||||||
|
<img src="@assets/common/images/comment-active.png"> @commentCount
|
||||||
|
</a>
|
||||||
|
} else {
|
||||||
|
<a href="@path/@issue.userName/@issue.repositoryName/issues/@issue.issueId" class="issue-comment-count" style="color: silver;">
|
||||||
|
<img src="@assets/common/images/comment.png"> @commentCount
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
<div class="small muted" style="margin-left: 20px; margin-top: 5px;">
|
||||||
|
#@issue.issueId opened by @user(issue.openedUserName, styleClass="username") @datetime(issue.registeredDate)
|
||||||
|
@milestone.map { milestone =>
|
||||||
|
<span style="margin: 20px;"><a href="@condition.copy(milestoneId = Some(Some(1))).toURL" class="username"><img src="@assets/common/images/milestone.png"> @milestone</a></span>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@if(hasWritePermission){
|
|
||||||
</label>
|
|
||||||
}
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
</table>
|
</table>
|
||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
@helper.html.paginator(page, (if(condition.state == "open") openCount else closedCount), service.IssuesService.IssueLimit, 10, condition.toURL)
|
@helper.html.paginator(page, (if(condition.state == "open") openCount else closedCount), service.IssuesService.IssueLimit, 10, condition.toURL)
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|||||||
22
src/main/twirl/dashboard/issuesnavi.scala.html
Normal file
22
src/main/twirl/dashboard/issuesnavi.scala.html
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
@(filter: String,
|
||||||
|
active: String,
|
||||||
|
condition: service.IssuesService.IssueSearchCondition)(implicit context: app.Context)
|
||||||
|
@import context._
|
||||||
|
@import view.helpers._
|
||||||
|
<ul class="nav nav-pills-group pull-left fill-width">
|
||||||
|
<li class="@if(filter == "created_by"){active} first">
|
||||||
|
<a href="@path/dashboard/@active/created_by@condition.copy(author = None, assigned = None).toURL">Created</a>
|
||||||
|
</li>
|
||||||
|
<li class="@if(filter == "assigned"){active}">
|
||||||
|
<a href="@path/dashboard/@active/assigned@condition.copy(author = None, assigned = None).toURL">Assigned</a>
|
||||||
|
</li>
|
||||||
|
<li class="@if(filter == "mentioned"){active} last">
|
||||||
|
<a href="@path/dashboard/@active/mentioned@condition.copy(author = None, assigned = None).toURL">Mentioned</a>
|
||||||
|
</li>
|
||||||
|
<li class="pull-right">
|
||||||
|
<form method="GET" id="search-filter-form" action="@path/dashboard/@active" style="margin-bottom: 0px;">
|
||||||
|
<input type="text" id="search-filter-box" class="input-xlarge" name="q" style="height: 24px; width: 400px;"
|
||||||
|
value="is:@{if(active == "issues") "issue" else "pr"} @condition.toFilterString"/>
|
||||||
|
</form>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
@@ -1,42 +1,16 @@
|
|||||||
@(listparts: play.twirl.api.Html,
|
@(issues: List[service.IssuesService.IssueInfo],
|
||||||
counts: List[service.PullRequestService.PullRequestCount],
|
page: Int,
|
||||||
repositories: List[(String, String, Int)],
|
openCount: Int,
|
||||||
|
closedCount: Int,
|
||||||
condition: service.IssuesService.IssueSearchCondition,
|
condition: service.IssuesService.IssueSearchCondition,
|
||||||
filter: String)(implicit context: app.Context)
|
filter: String,
|
||||||
|
groups: List[String])(implicit context: app.Context)
|
||||||
@import context._
|
@import context._
|
||||||
@import view.helpers._
|
@import view.helpers._
|
||||||
@html.main("Your Issues"){
|
@html.main("Pull Requests"){
|
||||||
<div class="container">
|
|
||||||
@dashboard.html.tab("pulls")
|
@dashboard.html.tab("pulls")
|
||||||
<div class="row-fluid">
|
<div class="container">
|
||||||
<div class="span3">
|
@issuesnavi(filter, "pulls", condition)
|
||||||
<ul class="nav nav-pills nav-stacked">
|
@pullslist(issues, page, openCount, closedCount, condition, filter, groups)
|
||||||
<li@if(filter == "created_by"){ class="active"}>
|
|
||||||
<a href="@path/dashboard/pulls/owned@condition.toURL">
|
|
||||||
<span class="count-right">@counts.find(_.userName == loginAccount.get.userName).map(_.count).getOrElse(0)</span>
|
|
||||||
Yours
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li@if(filter == "not_created_by"){ class="active"}>
|
|
||||||
<a href="@path/dashboard/pulls/public@condition.toURL">
|
|
||||||
<span class="count-right">@counts.filter(_.userName != loginAccount.get.userName).map(_.count).sum</span>
|
|
||||||
Public
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<hr/>
|
|
||||||
<ul class="nav nav-pills nav-stacked small">
|
|
||||||
@repositories.map { case (owner, name, count) =>
|
|
||||||
<li@if(condition.repo == Some(owner + "/" + name)){ class="active"}>
|
|
||||||
<a href="@path/dashboard/pulls/for/@owner/@name">
|
|
||||||
<span class="count-right">@count</span>
|
|
||||||
@owner/@name
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
@listparts
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,75 +3,42 @@
|
|||||||
openCount: Int,
|
openCount: Int,
|
||||||
closedCount: Int,
|
closedCount: Int,
|
||||||
condition: service.IssuesService.IssueSearchCondition,
|
condition: service.IssuesService.IssueSearchCondition,
|
||||||
repository: Option[service.RepositoryService.RepositoryInfo],
|
filter: String,
|
||||||
hasWritePermission: Boolean)(implicit context: app.Context)
|
groups: List[String])(implicit context: app.Context)
|
||||||
@import context._
|
@import context._
|
||||||
@import view.helpers._
|
@import view.helpers._
|
||||||
@import service.IssuesService.IssueInfo
|
@import service.IssuesService.IssueInfo
|
||||||
<div class="span9">
|
@*
|
||||||
@repository.map { repository =>
|
<ul class="nav nav-pills-group pull-left fill-width">
|
||||||
@if(hasWritePermission){
|
<li class="@if(filter == "created_by"){active} first"><a href="@path/dashboard/pulls/created_by@condition.toURL">Created</a></li>
|
||||||
<div class="pull-right">
|
<li class="@if(filter == "assigned"){active}"><a href="@path/dashboard/pulls/assigned@condition.toURL">Assigned</a></li>
|
||||||
@helper.html.paginator(page, (if(condition.state == "open") openCount else closedCount), service.PullRequestService.PullRequestLimit, 7, condition.toURL)
|
<li class="@if(filter == "mentioned"){active} last"><a href="@path/dashboard/pulls/mentioned@condition.toURL">Mentioned</a></li>
|
||||||
<a href="@url(repository)/compare" class="btn btn-small btn-success">New pull request</a>
|
<li class="pull-right">
|
||||||
</div>
|
<div class="input-prepend" style="margin-bottom: 0px;">
|
||||||
}
|
|
||||||
}
|
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<a class="btn btn-small@if(condition.state == "open"){ active}" href="@condition.copy(state = "open").toURL">@openCount Open</a>
|
<button type="button" class="btn dropdown-toggle" data-toggle="dropdown" style="height: 34px;">
|
||||||
<a class="btn btn-small@if(condition.state == "closed"){ active}" href="@condition.copy(state = "closed").toURL">@closedCount Closed</a>
|
Filter
|
||||||
|
<span class="caret"></span>
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li><a href="?q=is:open">Open issues and pull requests</a></li>
|
||||||
|
<li><a href="?q=is:open+is:issue+author:@urlEncode(loginAccount.get.userName)">Your issues</a></li>
|
||||||
|
<li><a href="?q=is:open+is:pr+author:@urlEncode(loginAccount.get.userName)">Your pull requests</a></li>
|
||||||
|
<li><a href="?q=is:open+assignee:@urlEncode(loginAccount.get.userName)">Everything assigned to you</a></li>
|
||||||
|
<li><a href="?q=is:open+mentions:@urlEncode(loginAccount.get.userName)">Everything mentioning you</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<input type="text" id="search-filter-box" class="input-xlarge" name="q" style="height: 24px;" value="is:@{if(active == "issues") "issue" else "pr"} @condition.toFilterString"/>
|
||||||
</div>
|
</div>
|
||||||
@helper.html.dropdown(
|
|
||||||
value = (condition.sort, condition.direction) match {
|
|
||||||
case ("created" , "desc") => "Newest"
|
|
||||||
case ("created" , "asc" ) => "Oldest"
|
|
||||||
case ("comments", "desc") => "Most commented"
|
|
||||||
case ("comments", "asc" ) => "Least commented"
|
|
||||||
case ("updated" , "desc") => "Recently updated"
|
|
||||||
case ("updated" , "asc" ) => "Least recently updated"
|
|
||||||
},
|
|
||||||
prefix = "Sort",
|
|
||||||
mini = false
|
|
||||||
){
|
|
||||||
<li>
|
|
||||||
<a href="@condition.copy(sort="created", direction="desc").toURL">
|
|
||||||
@helper.html.checkicon(condition.sort == "created" && condition.direction == "desc") Newest
|
|
||||||
</a>
|
|
||||||
</li>
|
</li>
|
||||||
<li>
|
</ul>
|
||||||
<a href="@condition.copy(sort="created", direction="asc" ).toURL">
|
*@
|
||||||
@helper.html.checkicon(condition.sort == "created" && condition.direction == "asc") Oldest
|
<table class="table table-bordered table-hover table-issues">
|
||||||
</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>
|
|
||||||
}
|
|
||||||
<table class="table table-bordered table-hover table-issues">
|
|
||||||
@if(issues.isEmpty){
|
|
||||||
<tr>
|
<tr>
|
||||||
<td style="padding: 20px; background-color: #eee; text-align: center;">
|
<th style="background-color: #eee;">
|
||||||
No pull requests to show.
|
@dashboard.html.header(openCount, closedCount, condition, groups)
|
||||||
</td>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
|
||||||
@issues.map { case IssueInfo(issue, labels, milestone, commentCount) =>
|
@issues.map { case IssueInfo(issue, labels, milestone, commentCount) =>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
@@ -94,8 +61,7 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
</table>
|
</table>
|
||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
@helper.html.paginator(page, (if(condition.state == "open") openCount else closedCount), service.PullRequestService.PullRequestLimit, 10, condition.toURL)
|
@helper.html.paginator(page, (if(condition.state == "open") openCount else closedCount), service.PullRequestService.PullRequestLimit, 10, condition.toURL)
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
@@ -1,13 +1,47 @@
|
|||||||
@(active: String = "")(implicit context: app.Context)
|
@(active: String = "")(implicit context: app.Context)
|
||||||
@import context._
|
@import context._
|
||||||
@import view.helpers._
|
@import view.helpers._
|
||||||
<ul class="nav nav-tabs">
|
<div class="dashboard-nav">
|
||||||
<li@if(active == ""){ class="active"}><a href="@path/">News Feed</a></li>
|
<div class="container">
|
||||||
|
<a href="@path/" @if(active == ""){ class="active"}>
|
||||||
|
<img src="@assets/common/images/menu-feed.png">
|
||||||
|
News Feed
|
||||||
|
</a>
|
||||||
@if(loginAccount.isDefined){
|
@if(loginAccount.isDefined){
|
||||||
<li@if(active == "pulls" ){ class="active"}><a href="@path/dashboard/pulls">Pull Requests</a></li>
|
<a href="@path/dashboard/pulls" @if(active == "pulls" ){ class="active"}>
|
||||||
<li@if(active == "issues"){ class="active"}><a href="@path/dashboard/issues/repos">Issues</a></li>
|
<img src="@assets/common/images/menu-pulls.png">
|
||||||
|
Pull Requests
|
||||||
|
</a>
|
||||||
|
<a href="@path/dashboard/issues" @if(active == "issues"){ class="active"}>
|
||||||
|
<img src="@assets/common/images/menu-issues.png">
|
||||||
|
Issues
|
||||||
|
</a>
|
||||||
}
|
}
|
||||||
@if(active == ""){
|
</div>
|
||||||
<li class="pull-right"><a href="@path/activities.atom"><img src="@assets/common/images/feed.png" alt="activities"></a></li>
|
</div>
|
||||||
}
|
<style type="text/css">
|
||||||
</ul>
|
div.dashboard-nav {
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
text-align: right;
|
||||||
|
height: 32px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.dashboard-nav a {
|
||||||
|
line-height: 10px;
|
||||||
|
margin-left: 20px;
|
||||||
|
padding-bottom: 13px;
|
||||||
|
padding-left: 4px;
|
||||||
|
padding-right: 4px;
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.dashboard-nav a:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.dashboard-nav a.active {
|
||||||
|
border-bottom: 2px solid #bb4444;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -62,7 +62,7 @@
|
|||||||
@detailActivity(activity: model.Activity, image: String) = {
|
@detailActivity(activity: model.Activity, image: String) = {
|
||||||
<div class="activity-icon-large"><img src="@assets/common/images/@image"/></div>
|
<div class="activity-icon-large"><img src="@assets/common/images/@image"/></div>
|
||||||
<div class="activity-content">
|
<div class="activity-content">
|
||||||
<div class="muted small">@datetime(activity.activityDate)</div>
|
<div class="muted small">@helper.html.datetimeago(activity.activityDate)</div>
|
||||||
<div class="strong">
|
<div class="strong">
|
||||||
@avatar(activity.activityUserName, 16)
|
@avatar(activity.activityUserName, 16)
|
||||||
@activityMessage(activity.message)
|
@activityMessage(activity.message)
|
||||||
@@ -76,7 +76,7 @@
|
|||||||
@customActivity(activity: model.Activity, image: String)(additionalInfo: Any) = {
|
@customActivity(activity: model.Activity, image: String)(additionalInfo: Any) = {
|
||||||
<div class="activity-icon-large"><img src="@assets/common/images/@image"/></div>
|
<div class="activity-icon-large"><img src="@assets/common/images/@image"/></div>
|
||||||
<div class="activity-content">
|
<div class="activity-content">
|
||||||
<div class="muted small">@datetime(activity.activityDate)</div>
|
<div class="muted small">@helper.html.datetimeago(activity.activityDate)</div>
|
||||||
<div class="strong">
|
<div class="strong">
|
||||||
@avatar(activity.activityUserName, 16)
|
@avatar(activity.activityUserName, 16)
|
||||||
@activityMessage(activity.message)
|
@activityMessage(activity.message)
|
||||||
@@ -91,7 +91,7 @@
|
|||||||
<div>
|
<div>
|
||||||
@avatar(activity.activityUserName, 16)
|
@avatar(activity.activityUserName, 16)
|
||||||
@activityMessage(activity.message)
|
@activityMessage(activity.message)
|
||||||
<span class="muted small">@datetime(activity.activityDate)</span>
|
<span class="muted small">@helper.html.datetimeago(activity.activityDate)</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
62
src/main/twirl/helper/branchcontrol.scala.html
Normal file
62
src/main/twirl/helper/branchcontrol.scala.html
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
@(branch: String = "",
|
||||||
|
repository: service.RepositoryService.RepositoryInfo,
|
||||||
|
hasWritePermission: Boolean)(body: Html)(implicit context: app.Context)
|
||||||
|
@import context._
|
||||||
|
@import view.helpers._
|
||||||
|
@helper.html.dropdown(
|
||||||
|
value = if(branch.length == 40) branch.substring(0, 10) else branch,
|
||||||
|
prefix = if(branch.length == 40) "tree" else if(repository.branchList.contains(branch)) "branch" else "tree",
|
||||||
|
mini = true
|
||||||
|
) {
|
||||||
|
<li><div id="branch-control-title">Switch branches<button id="branch-control-close" class="pull-right">×</button></div></li>
|
||||||
|
<li><input id="branch-control-input" type="text" placeholder="Find or create branch ..."/></li>
|
||||||
|
@body
|
||||||
|
@if(hasWritePermission) {
|
||||||
|
<li id="create-branch" style="display: none;">
|
||||||
|
<a><form action="@url(repository)/branches" method="post" style="margin: 0;">
|
||||||
|
<span class="new-branch-name">Create branch: <span class="new-branch"></span></span>
|
||||||
|
<br><span style="padding-left: 17px;">from '@branch'</span>
|
||||||
|
<input type="hidden" name="new">
|
||||||
|
<input type="hidden" name="from" value="@branch">
|
||||||
|
</form></a>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
<script>
|
||||||
|
$(function(){
|
||||||
|
$('#branch-control-input').parent().click(function(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
});
|
||||||
|
$('#branch-control-close').click(function() {
|
||||||
|
$('[data-toggle="dropdown"]').parent().removeClass('open');
|
||||||
|
});
|
||||||
|
$('#branch-control-input').keyup(function() {
|
||||||
|
var inputVal = $('#branch-control-input').val();
|
||||||
|
$.each($('#branch-control-input').parent().parent().find('a'), function(index, elem) {
|
||||||
|
if (!inputVal || !elem.text.trim() || elem.text.trim().lastIndexOf(inputVal, 0) >= 0) {
|
||||||
|
$(elem).parent().show();
|
||||||
|
} else {
|
||||||
|
$(elem).parent().hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
@if(hasWritePermission) {
|
||||||
|
if (inputVal) {
|
||||||
|
$('#create-branch').parent().find('li:last-child').show().find('.new-branch').text(inputVal);
|
||||||
|
} else {
|
||||||
|
$('#create-branch').parent().find('li:last-child').hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
@if(hasWritePermission) {
|
||||||
|
$('#create-branch').click(function() {
|
||||||
|
$(this).find('input[name="new"]').val($('.dropdown-menu input').val())
|
||||||
|
$(this).find('form').submit()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
$('.btn-group').click(function() {
|
||||||
|
$('#branch-control-input').val('');
|
||||||
|
$('.dropdown-menu li').show();
|
||||||
|
$('#create-branch').hide();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
10
src/main/twirl/helper/datetimeago.scala.html
Normal file
10
src/main/twirl/helper/datetimeago.scala.html
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
@(latestUpdatedDate: java.util.Date,
|
||||||
|
recentOnly: Boolean = true)
|
||||||
|
@import view.helpers._
|
||||||
|
<span data-toggle="tooltip" title="@datetime(latestUpdatedDate)">
|
||||||
|
@if(recentOnly){
|
||||||
|
@datetimeAgoRecentOnly(latestUpdatedDate)
|
||||||
|
}else{
|
||||||
|
@datetimeAgo(latestUpdatedDate)
|
||||||
|
}
|
||||||
|
</span>
|
||||||
7
src/main/twirl/helper/error.scala.html
Normal file
7
src/main/twirl/helper/error.scala.html
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
@(error: Option[Any])
|
||||||
|
@if(error.isDefined){
|
||||||
|
<div class='alert alert-danger'>
|
||||||
|
<button type="button" class="close" data-dismiss="alert">×</button>
|
||||||
|
@error
|
||||||
|
</div>
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
@(info: Option[Any])
|
@(info: Option[Any])
|
||||||
@if(info.isDefined){
|
@if(info.isDefined){
|
||||||
<div class="alert alert-info">
|
<div class="alert alert-info">
|
||||||
<button type="button" class="close" data-dismiss="alert">×</button>
|
<button type="button" class="close" data-dismiss="alert">×</button>
|
||||||
@info
|
@info
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@(repository: service.RepositoryService.RepositoryInfo, content: String, enableWikiLink: Boolean, enableRefsLink: Boolean,
|
@(repository: service.RepositoryService.RepositoryInfo, content: String, enableWikiLink: Boolean, enableRefsLink: Boolean, enableTaskList: Boolean, hasWritePermission: Boolean,
|
||||||
style: String = "", placeholder: String = "Leave a comment", elastic: Boolean = false)(implicit context: app.Context)
|
style: String = "", placeholder: String = "Leave a comment", elastic: Boolean = false)(implicit context: app.Context)
|
||||||
@import context._
|
@import context._
|
||||||
@import view.helpers._
|
@import view.helpers._
|
||||||
@@ -38,7 +38,8 @@ $(function(){
|
|||||||
$.post('@url(repository)/_preview', {
|
$.post('@url(repository)/_preview', {
|
||||||
content : $('#content').val(),
|
content : $('#content').val(),
|
||||||
enableWikiLink : @enableWikiLink,
|
enableWikiLink : @enableWikiLink,
|
||||||
enableRefsLink : @enableRefsLink
|
enableRefsLink : @enableRefsLink,
|
||||||
|
enableTaskList : @enableTaskList
|
||||||
}, function(data){
|
}, function(data){
|
||||||
$('#preview-area').html(data);
|
$('#preview-area').html(data);
|
||||||
prettyPrint();
|
prettyPrint();
|
||||||
|
|||||||
@@ -4,13 +4,23 @@
|
|||||||
@import context._
|
@import context._
|
||||||
@import view.helpers._
|
@import view.helpers._
|
||||||
@main("GitBucket"){
|
@main("GitBucket"){
|
||||||
<div class="container">
|
|
||||||
@dashboard.html.tab()
|
@dashboard.html.tab()
|
||||||
|
<div class="container">
|
||||||
<div class="row-fluid">
|
<div class="row-fluid">
|
||||||
<div class="span8">
|
<div class="span8">
|
||||||
|
<div class="pull-right">
|
||||||
|
<a href="@path/activities.atom"><img src="@assets/common/images/feed.png" alt="activities"></a>
|
||||||
|
</div>
|
||||||
@helper.html.activities(activities)
|
@helper.html.activities(activities)
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="span4">
|
<div class="span4">
|
||||||
|
@settings.information.map { information =>
|
||||||
|
<div class="alert alert-info" style="background-color: white; color: #555; border-color: #4183c4; font-size: small; line-height: 120%;">
|
||||||
|
<button type="button" class="close" data-dismiss="alert">×</button>
|
||||||
|
@Html(information)
|
||||||
|
</div>
|
||||||
|
}
|
||||||
@if(loginAccount.isEmpty){
|
@if(loginAccount.isEmpty){
|
||||||
@signinform(settings)
|
@signinform(settings)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
<div class="issue-avatar-image">@avatar(loginAccount.get.userName, 48)</div>
|
<div class="issue-avatar-image">@avatar(loginAccount.get.userName, 48)</div>
|
||||||
<div class="box issue-comment-box">
|
<div class="box issue-comment-box">
|
||||||
<div class="box-content">
|
<div class="box-content">
|
||||||
@helper.html.preview(repository, "", false, true, "width: 635px; height: 100px; max-height: 150px;", elastic = true)
|
@helper.html.preview(repository, "", false, true, true, hasWritePermission, "width: 635px; height: 100px; max-height: 150px;", elastic = true)
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<div class="issue-avatar-image">@avatar(issue.openedUserName, 48)</div>
|
<div class="issue-avatar-image">@avatar(issue.openedUserName, 48)</div>
|
||||||
<div class="box issue-comment-box">
|
<div class="box issue-comment-box">
|
||||||
<div class="box-header-small">
|
<div class="box-header-small">
|
||||||
@user(issue.openedUserName, styleClass="username strong") <span class="muted">commented on @datetime(issue.registeredDate)</span>
|
@user(issue.openedUserName, styleClass="username strong") <span class="muted">commented @helper.html.datetimeago(issue.registeredDate)</span>
|
||||||
<span class="pull-right">
|
<span class="pull-right">
|
||||||
@if(hasWritePermission || loginAccount.map(_.userName == issue.openedUserName).getOrElse(false)){
|
@if(hasWritePermission || loginAccount.map(_.userName == issue.openedUserName).getOrElse(false)){
|
||||||
<a href="#" data-issue-id="@issue.issueId"><i class="icon-pencil"></i></a>
|
<a href="#" data-issue-id="@issue.issueId"><i class="icon-pencil"></i></a>
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="box-content issue-content" id="issueContent">
|
<div class="box-content issue-content" id="issueContent">
|
||||||
@markdown(issue.content getOrElse "No description provided.", repository, false, true)
|
@markdown(issue.content getOrElse "No description provided.", repository, false, true, true, hasWritePermission)
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
} else {
|
} else {
|
||||||
@if(pullreq.isEmpty){ referenced the issue } else { referenced the pull request }
|
@if(pullreq.isEmpty){ referenced the issue } else { referenced the pull request }
|
||||||
}
|
}
|
||||||
on @datetime(comment.registeredDate)
|
@helper.html.datetimeago(comment.registeredDate)
|
||||||
</span>
|
</span>
|
||||||
<span class="pull-right">
|
<span class="pull-right">
|
||||||
@if(comment.action != "commit" && comment.action != "merge" && comment.action != "refer" &&
|
@if(comment.action != "commit" && comment.action != "merge" && comment.action != "refer" &&
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
@if(comment.action == "commit" && comment.content.split(" ").last.matches("[a-f0-9]{40}")){
|
@if(comment.action == "commit" && comment.content.split(" ").last.matches("[a-f0-9]{40}")){
|
||||||
@defining(comment.content.substring(comment.content.length - 40)){ id =>
|
@defining(comment.content.substring(comment.content.length - 40)){ id =>
|
||||||
<div class="pull-right"><a href="@path/@repository.owner/@repository.name/commit/@id" class="monospace">@id.substring(0, 7)</a></div>
|
<div class="pull-right"><a href="@path/@repository.owner/@repository.name/commit/@id" class="monospace">@id.substring(0, 7)</a></div>
|
||||||
@markdown(comment.content.substring(0, comment.content.length - 41), repository, false, true)
|
@markdown(comment.content.substring(0, comment.content.length - 41), repository, false, true, true, hasWritePermission)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@if(comment.action == "refer"){
|
@if(comment.action == "refer"){
|
||||||
@@ -54,7 +54,7 @@
|
|||||||
<strong><a href="@path/@repository.owner/@repository.name/issues/@issueId">Issue #@issueId</a>: @rest.mkString(":")</strong>
|
<strong><a href="@path/@repository.owner/@repository.name/issues/@issueId">Issue #@issueId</a>: @rest.mkString(":")</strong>
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@markdown(comment.content, repository, false, true)
|
@markdown(comment.content, repository, false, true, true, hasWritePermission)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@@ -70,7 +70,7 @@
|
|||||||
} else {
|
} else {
|
||||||
<span class="label label-info monospace">@pullreq.map(_.userName):@pullreq.map(_.branch)</span> to <span class="label label-info monospace">@pullreq.map(_.requestUserName):@pullreq.map(_.requestBranch)</span>
|
<span class="label label-info monospace">@pullreq.map(_.userName):@pullreq.map(_.branch)</span> to <span class="label label-info monospace">@pullreq.map(_.requestUserName):@pullreq.map(_.requestBranch)</span>
|
||||||
}
|
}
|
||||||
@datetime(comment.registeredDate)
|
@helper.html.datetimeago(comment.registeredDate)
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@if(comment.action == "close" || comment.action == "close_comment"){
|
@if(comment.action == "close" || comment.action == "close_comment"){
|
||||||
@@ -78,9 +78,9 @@
|
|||||||
<span class="label label-important">Closed</span>
|
<span class="label label-important">Closed</span>
|
||||||
@avatar(comment.commentedUserName, 20)
|
@avatar(comment.commentedUserName, 20)
|
||||||
@if(issue.isPullRequest){
|
@if(issue.isPullRequest){
|
||||||
@user(comment.commentedUserName, styleClass="username strong") closed the pull request @datetime(comment.registeredDate)
|
@user(comment.commentedUserName, styleClass="username strong") closed the pull request @helper.html.datetimeago(comment.registeredDate)
|
||||||
} else {
|
} else {
|
||||||
@user(comment.commentedUserName, styleClass="username strong") closed the issue @datetime(comment.registeredDate)
|
@user(comment.commentedUserName, styleClass="username strong") closed the issue @helper.html.datetimeago(comment.registeredDate)
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -88,14 +88,14 @@
|
|||||||
<div class="small issue-comment-action">
|
<div class="small issue-comment-action">
|
||||||
<span class="label label-success">Reopened</span>
|
<span class="label label-success">Reopened</span>
|
||||||
@avatar(comment.commentedUserName, 20)
|
@avatar(comment.commentedUserName, 20)
|
||||||
@user(comment.commentedUserName, styleClass="username strong") reopened the issue @datetime(comment.registeredDate)
|
@user(comment.commentedUserName, styleClass="username strong") reopened the issue @helper.html.datetimeago(comment.registeredDate)
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@if(comment.action == "delete_branch"){
|
@if(comment.action == "delete_branch"){
|
||||||
<div class="small issue-comment-action">
|
<div class="small issue-comment-action">
|
||||||
<span class="label">Deleted</span>
|
<span class="label">Deleted</span>
|
||||||
@avatar(comment.commentedUserName, 20)
|
@avatar(comment.commentedUserName, 20)
|
||||||
@user(comment.commentedUserName, styleClass="username strong") deleted the <span class="label label-info monospace">@pullreq.map(_.requestBranch)</span> branch @datetime(comment.registeredDate)
|
@user(comment.commentedUserName, styleClass="username strong") deleted the <span class="label label-info monospace">@pullreq.map(_.requestBranch)</span> branch @helper.html.datetimeago(comment.registeredDate)
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -134,5 +134,67 @@ $(function(){
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var extractMarkdown = function(data){
|
||||||
|
$('body').append('<div id="tmp"></div>');
|
||||||
|
$('#tmp').html(data);
|
||||||
|
var markdown = $('#tmp textarea').val();
|
||||||
|
$('#tmp').remove();
|
||||||
|
return markdown;
|
||||||
|
};
|
||||||
|
|
||||||
|
var replaceTaskList = function(issueContentHtml, checkboxes) {
|
||||||
|
var ss = [],
|
||||||
|
markdown = extractMarkdown(issueContentHtml),
|
||||||
|
xs = markdown.split(/- \[[x| ]\]/g);
|
||||||
|
for (var i=0; i<xs.length; i++) {
|
||||||
|
ss.push(xs[i]);
|
||||||
|
if (checkboxes.eq(i).prop('checked')) ss.push('- [x]');
|
||||||
|
else ss.push('- [ ]');
|
||||||
|
}
|
||||||
|
ss.pop();
|
||||||
|
return ss.join('');
|
||||||
|
};
|
||||||
|
|
||||||
|
$('#issueContent').on('click', ':checkbox', function(ev){
|
||||||
|
var checkboxes = $('#issueContent :checkbox');
|
||||||
|
$.get('@url(repository)/issues/_data/@issue.issueId',
|
||||||
|
{
|
||||||
|
dataType : 'html'
|
||||||
|
},
|
||||||
|
function(responseContent){
|
||||||
|
$.ajax({
|
||||||
|
url: '@url(repository)/issues/edit/@issue.issueId',
|
||||||
|
type: 'POST',
|
||||||
|
data: {
|
||||||
|
title : $('#issueTitle').text(),
|
||||||
|
content : replaceTaskList(responseContent, checkboxes)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
$('div[id^=commentContent-]').on('click', ':checkbox', function(ev){
|
||||||
|
var $commentContent = $(ev.target).parents('div[id^=commentContent-]'),
|
||||||
|
commentId = $commentContent.attr('id').replace(/commentContent-/, ''),
|
||||||
|
checkboxes = $commentContent.find(':checkbox');
|
||||||
|
$.get('@url(repository)/issue_comments/_data/' + commentId,
|
||||||
|
{
|
||||||
|
dataType : 'html'
|
||||||
|
},
|
||||||
|
function(responseContent){
|
||||||
|
$.ajax({
|
||||||
|
url: '@url(repository)/issue_comments/edit/' + commentId,
|
||||||
|
type: 'POST',
|
||||||
|
data: {
|
||||||
|
issueId : 0,
|
||||||
|
content : replaceTaskList(responseContent, checkboxes)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
@import view.helpers._
|
@import view.helpers._
|
||||||
@html.main(s"New Issue - ${repository.owner}/${repository.name}", Some(repository)){
|
@html.main(s"New Issue - ${repository.owner}/${repository.name}", Some(repository)){
|
||||||
@html.menu("issues", repository){
|
@html.menu("issues", repository){
|
||||||
@tab("issues", false, repository)
|
@navigation("issues", false, repository)
|
||||||
<br/><br/><hr style="margin-bottom: 10px;">
|
<br/><br/><hr style="margin-bottom: 10px;">
|
||||||
<form action="@url(repository)/issues/new" method="POST" validate="true">
|
<form action="@url(repository)/issues/new" method="POST" validate="true">
|
||||||
<div class="row-fluid">
|
<div class="row-fluid">
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
@helper.html.preview(repository, "", false, true, "width: 565px; height: 200px; max-height: 250px;", elastic = true)
|
@helper.html.preview(repository, "", false, true, true, hasWritePermission, "width: 565px; height: 200px; max-height: 250px;", elastic = true)
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
<span class="label label-success issue-status">Open</span>
|
<span class="label label-success issue-status">Open</span>
|
||||||
}
|
}
|
||||||
<span class="muted">
|
<span class="muted">
|
||||||
@user(issue.openedUserName, styleClass="username strong") opened this issue on @datetime(issue.registeredDate) - @defining(
|
@user(issue.openedUserName, styleClass="username strong") opened this issue @helper.html.datetimeago(issue.registeredDate) - @defining(
|
||||||
comments.filter( _.action.contains("comment") ).size
|
comments.filter( _.action.contains("comment") ).size
|
||||||
){ count =>
|
){ count =>
|
||||||
@count @plural(count, "comment")
|
@count @plural(count, "comment")
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
@import view.helpers._
|
@import view.helpers._
|
||||||
@html.main(s"Labels - ${repository.owner}/${repository.name}"){
|
@html.main(s"Labels - ${repository.owner}/${repository.name}"){
|
||||||
@html.menu("issues", repository){
|
@html.menu("issues", repository){
|
||||||
@issues.html.tab("labels", hasWritePermission, repository)
|
@issues.html.navigation("labels", hasWritePermission, repository)
|
||||||
|
<br>
|
||||||
<table class="table table-bordered table-hover table-issues" id="new-label-table" style="display: none;">
|
<table class="table table-bordered table-hover table-issues" id="new-label-table" style="display: none;">
|
||||||
<tr><td></td></tr>
|
<tr><td></td></tr>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
@import view.helpers._
|
@import view.helpers._
|
||||||
@html.main((if(target == "issues") "Issues" else "Pull requests") + s" - ${repository.owner}/${repository.name}", Some(repository)){
|
@html.main((if(target == "issues") "Issues" else "Pull requests") + s" - ${repository.owner}/${repository.name}", Some(repository)){
|
||||||
@html.menu(target, repository){
|
@html.menu(target, repository){
|
||||||
@tab(target, true, repository)
|
@navigation(target, true, repository, Some(condition))
|
||||||
@listparts(target, issues, page, openCount, closedCount, condition, collaborators, milestones, labels, Some(repository), hasWritePermission)
|
@listparts(target, issues, page, openCount, closedCount, condition, collaborators, milestones, labels, Some(repository), hasWritePermission)
|
||||||
@if(hasWritePermission){
|
@if(hasWritePermission){
|
||||||
<form id="batcheditForm" method="POST">
|
<form id="batcheditForm" method="POST">
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
@import context._
|
@import context._
|
||||||
@import view.helpers._
|
@import view.helpers._
|
||||||
@import service.IssuesService.IssueInfo
|
@import service.IssuesService.IssueInfo
|
||||||
|
<br>
|
||||||
@if(condition.nonEmpty){
|
@if(condition.nonEmpty){
|
||||||
<div>
|
<div>
|
||||||
<a href="@service.IssuesService.IssueSearchCondition().toURL" class="header-link">
|
<a href="@service.IssuesService.IssueSearchCondition().toURL" class="header-link">
|
||||||
@@ -21,7 +22,6 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
<table class="table table-bordered table-hover table-issues">
|
<table class="table table-bordered table-hover table-issues">
|
||||||
<tr>
|
<tr>
|
||||||
<th style="background-color: #eee;">
|
<th style="background-color: #eee;">
|
||||||
@@ -203,7 +203,7 @@
|
|||||||
}
|
}
|
||||||
</span>
|
</span>
|
||||||
<div class="small muted" style="margin-left: 40px; margin-top: 5px;">
|
<div class="small muted" style="margin-left: 40px; margin-top: 5px;">
|
||||||
#@issue.issueId opened by @user(issue.openedUserName, styleClass="username") @datetime(issue.registeredDate)
|
#@issue.issueId opened @helper.html.datetimeago(issue.registeredDate) by @user(issue.openedUserName, styleClass="username")
|
||||||
@milestone.map { milestone =>
|
@milestone.map { milestone =>
|
||||||
<span style="margin: 20px;"><a href="@condition.copy(milestoneId = Some(Some(1))).toURL" class="username"><img src="@assets/common/images/milestone.png"> @milestone</a></span>
|
<span style="margin: 20px;"><a href="@condition.copy(milestoneId = Some(Some(1))).toURL" class="username"><img src="@assets/common/images/milestone.png"> @milestone</a></span>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<h4>New milestone</h4>
|
<h4>New milestone</h4>
|
||||||
<div class="muted">Create a new milestone to help organize your issues and pull requests.</div>
|
<div class="muted">Create a new milestone to help organize your issues and pull requests.</div>
|
||||||
} else {
|
} else {
|
||||||
@issues.html.tab("milestones", false, repository)
|
@issues.html.navigation("milestones", false, repository)
|
||||||
<br><br>
|
<br><br>
|
||||||
}
|
}
|
||||||
<hr style="margin-top: 12px; margin-bottom: 18px;" class="fill-width"/>
|
<hr style="margin-top: 12px; margin-bottom: 18px;" class="fill-width"/>
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
@import view.helpers._
|
@import view.helpers._
|
||||||
@html.main(s"Milestones - ${repository.owner}/${repository.name}"){
|
@html.main(s"Milestones - ${repository.owner}/${repository.name}"){
|
||||||
@html.menu("issues", repository){
|
@html.menu("issues", repository){
|
||||||
@issues.html.tab("milestones", hasWritePermission, repository)
|
@issues.html.navigation("milestones", hasWritePermission, repository)
|
||||||
|
<br>
|
||||||
<table class="table table-bordered table-hover table-issues">
|
<table class="table table-bordered table-hover table-issues">
|
||||||
<tr>
|
<tr>
|
||||||
<th style="background-color: #eee;">
|
<th style="background-color: #eee;">
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
<a href="@url(repository)/issues?milestone=@milestone.milestoneId&state=open" class="milestone-title">@milestone.title</a>
|
<a href="@url(repository)/issues?milestone=@milestone.milestoneId&state=open" class="milestone-title">@milestone.title</a>
|
||||||
<div style="margin-top: 6px">
|
<div style="margin-top: 6px">
|
||||||
@if(milestone.closedDate.isDefined){
|
@if(milestone.closedDate.isDefined){
|
||||||
<span class="muted">Closed @datetime(milestone.closedDate.get)</span>
|
<span class="muted">Closed @helper.html.datetimeago(milestone.closedDate.get)</span>
|
||||||
} else {
|
} else {
|
||||||
@milestone.dueDate.map { dueDate =>
|
@milestone.dueDate.map { dueDate =>
|
||||||
@if(isPast(dueDate)){
|
@if(isPast(dueDate)){
|
||||||
|
|||||||
58
src/main/twirl/issues/navigation.scala.html
Normal file
58
src/main/twirl/issues/navigation.scala.html
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
@(active: String,
|
||||||
|
newButton: Boolean,
|
||||||
|
repository: service.RepositoryService.RepositoryInfo,
|
||||||
|
condition: Option[service.IssuesService.IssueSearchCondition] = None)(implicit context: app.Context)
|
||||||
|
@import context._
|
||||||
|
@import view.helpers._
|
||||||
|
<ul class="nav nav-pills-group pull-left fill-width">
|
||||||
|
<li class="@if(active == "issues" ){active} first"><a href="@url(repository)/issues">Issues</a></li>
|
||||||
|
<li class="@if(active == "pulls" ){active}"><a href="@url(repository)/pulls">Pull requests</a></li>
|
||||||
|
<li class="@if(active == "labels" ){active}"><a href="@url(repository)/issues/labels">Labels</a></li>
|
||||||
|
<li class="@if(active == "milestones"){active} last"><a href="@url(repository)/issues/milestones">Milestones</a></li>
|
||||||
|
<li class="pull-right">
|
||||||
|
<form method="GET" id="search-filter-form" style="margin-bottom: 0px;">
|
||||||
|
@condition.map { condition =>
|
||||||
|
@if(loginAccount.isDefined){
|
||||||
|
<div class="input-prepend" style="margin-bottom: 0px;">
|
||||||
|
<div class="btn-group">
|
||||||
|
<button type="button" class="btn dropdown-toggle" data-toggle="dropdown" style="height: 34px;">
|
||||||
|
Filter
|
||||||
|
<span class="caret"></span>
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li><a href="?q=is:open">Open issues and pull requests</a></li>
|
||||||
|
<li><a href="?q=is:open+is:issue+author:@urlEncode(loginAccount.get.userName)">Your issues</a></li>
|
||||||
|
<li><a href="?q=is:open+is:pr+author:@urlEncode(loginAccount.get.userName)">Your pull requests</a></li>
|
||||||
|
<li><a href="?q=is:open+assignee:@urlEncode(loginAccount.get.userName)">Everything assigned to you</a></li>
|
||||||
|
@*
|
||||||
|
<li><a href="?q=is:open+mentions:@urlEncode(loginAccount.get.userName)">Everything mentioning you</a></li>
|
||||||
|
*@
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<input type="text" id="search-filter-box" class="input-xlarge" name="q" style="height: 24px;" value="is:@{if(active == "issues") "issue" else "pr"} @condition.toFilterString"/>
|
||||||
|
</div>
|
||||||
|
} else {
|
||||||
|
<input type="text" id="search-filter-box" class="input-xlarge" name="q" style="height: 24px;" value="is:@{if(active == "issues") "issue" else "pr"} @condition.toFilterString"/>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@if(loginAccount.isDefined){
|
||||||
|
<div class="btn-group">
|
||||||
|
@if(newButton){
|
||||||
|
@if(active == "issues"){
|
||||||
|
<a class="btn btn-success" href="@url(repository)/issues/new" style="height: 24px;">New issue</a>
|
||||||
|
}
|
||||||
|
@if(active == "pulls"){
|
||||||
|
<a class="btn btn-success" href="@url(repository)/compare" style="height: 24px;">New pull request</a>
|
||||||
|
}
|
||||||
|
@if(active == "labels"){
|
||||||
|
<a class="btn btn-success" href="javascript:void(0);" id="new-label-button" style="height: 24px;">New label</a>
|
||||||
|
}
|
||||||
|
@if(active == "milestones"){
|
||||||
|
<a class="btn btn-success" href="@url(repository)/issues/milestones/new" style="height: 24px;">New milestone</a>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</form>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
@(active: String, newButton: Boolean,
|
|
||||||
repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context)
|
|
||||||
@import context._
|
|
||||||
@import view.helpers._
|
|
||||||
<ul class="nav nav-pills-group pull-left fill-width">
|
|
||||||
<li class="@if(active == "issues" ){active} first"><a href="@url(repository)/issues">Issues</a></li>
|
|
||||||
<li class="@if(active == "pulls" ){active}"><a href="@url(repository)/pulls">Pull requests</a></li>
|
|
||||||
<li class="@if(active == "labels" ){active}"><a href="@url(repository)/issues/labels">Labels</a></li>
|
|
||||||
<li class="@if(active == "milestones"){active} last"><a href="@url(repository)/issues/milestones">Milestones</a></li>
|
|
||||||
@if(loginAccount.isDefined){
|
|
||||||
<li class="pull-right">
|
|
||||||
<div class="btn-group">
|
|
||||||
@if(newButton){
|
|
||||||
@if(active == "issues"){
|
|
||||||
<a class="btn btn-success" href="@url(repository)/issues/new">New issue</a>
|
|
||||||
}
|
|
||||||
@if(active == "pulls"){
|
|
||||||
<a class="btn btn-success" href="@url(repository)/compare">New pull request</a>
|
|
||||||
}
|
|
||||||
@if(active == "labels"){
|
|
||||||
<a class="btn btn-success" href="javascript:void(0);" id="new-label-button">New label</a>
|
|
||||||
}
|
|
||||||
@if(active == "milestones"){
|
|
||||||
<a class="btn btn-success" href="@url(repository)/issues/milestones/new">New milestone</a>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
@(active: String,
|
@(active: String,
|
||||||
repository: service.RepositoryService.RepositoryInfo,
|
repository: service.RepositoryService.RepositoryInfo,
|
||||||
id: Option[String] = None,
|
id: Option[String] = None,
|
||||||
expand: Boolean = false)(body: Html)(implicit context: app.Context)
|
expand: Boolean = false,
|
||||||
|
info: Option[Any] = None,
|
||||||
|
error: Option[Any] = None)(body: Html)(implicit context: app.Context)
|
||||||
@import context._
|
@import context._
|
||||||
@import view.helpers._
|
@import view.helpers._
|
||||||
|
|
||||||
@@ -31,6 +33,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
@helper.html.information(info)
|
||||||
|
@helper.html.error(error)
|
||||||
@if(repository.commitCount > 0){
|
@if(repository.commitCount > 0){
|
||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
<div class="input-prepend">
|
<div class="input-prepend">
|
||||||
|
|||||||
@@ -58,7 +58,7 @@
|
|||||||
<div style="width: 600px; border-right: 1px solid #d4d4d4;">
|
<div style="width: 600px; border-right: 1px solid #d4d4d4;">
|
||||||
<span class="error" id="error-title"></span>
|
<span class="error" id="error-title"></span>
|
||||||
<input type="text" name="title" style="width: 580px" placeholder="Title"/>
|
<input type="text" name="title" style="width: 580px" placeholder="Title"/>
|
||||||
@helper.html.preview(repository, "", false, true, "width: 580px; height: 200px;")
|
@helper.html.preview(repository, "", false, true, true, hasWritePermission, "width: 580px; height: 200px;")
|
||||||
<input type="hidden" name="targetUserName" value="@originRepository.owner"/>
|
<input type="hidden" name="targetUserName" value="@originRepository.owner"/>
|
||||||
<input type="hidden" name="targetBranch" value="@originId"/>
|
<input type="hidden" name="targetBranch" value="@originId"/>
|
||||||
<input type="hidden" name="requestUserName" value="@forkedRepository.owner"/>
|
<input type="hidden" name="requestUserName" value="@forkedRepository.owner"/>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
<span class="label label-info">Merged</span>
|
<span class="label label-info">Merged</span>
|
||||||
@user(comment.commentedUserName, styleClass="username strong") merged @commits.size @plural(commits.size, "commit")
|
@user(comment.commentedUserName, styleClass="username strong") merged @commits.size @plural(commits.size, "commit")
|
||||||
into <code>@pullreq.userName:@pullreq.branch</code> from <code>@pullreq.requestUserName:@pullreq.requestBranch</code>
|
into <code>@pullreq.userName:@pullreq.branch</code> from <code>@pullreq.requestUserName:@pullreq.requestBranch</code>
|
||||||
at @datetime(comment.registeredDate)
|
@helper.html.datetimeago(comment.registeredDate)
|
||||||
}.getOrElse {
|
}.getOrElse {
|
||||||
<span class="label label-important">Closed</span>
|
<span class="label label-important">Closed</span>
|
||||||
@user(issue.openedUserName, styleClass="username strong") wants to merge @commits.size @plural(commits.size, "commit")
|
@user(issue.openedUserName, styleClass="username strong") wants to merge @commits.size @plural(commits.size, "commit")
|
||||||
|
|||||||
@@ -9,10 +9,10 @@
|
|||||||
@html.main(s"${repository.owner}/${repository.name}", Some(repository)) {
|
@html.main(s"${repository.owner}/${repository.name}", Some(repository)) {
|
||||||
@html.menu("code", repository){
|
@html.menu("code", repository){
|
||||||
<div class="head">
|
<div class="head">
|
||||||
@helper.html.dropdown(
|
@helper.html.branchcontrol(
|
||||||
value = if(branch.length == 40) branch.substring(0, 10) else branch,
|
branch,
|
||||||
prefix = if(branch.length == 40) "tree" else if(repository.branchList.contains(branch)) "branch" else "tree",
|
repository,
|
||||||
mini = true
|
hasWritePermission
|
||||||
){
|
){
|
||||||
@repository.branchList.map { x =>
|
@repository.branchList.map { x =>
|
||||||
<li><a href="@url(repository)/blob/@encodeRefName(x)/@pathList.mkString("/")">@helper.html.checkicon(x == branch) @x</a></li>
|
<li><a href="@url(repository)/blob/@encodeRefName(x)/@pathList.mkString("/")">@helper.html.checkicon(x == branch) @x</a></li>
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
<div class="pull-left">
|
<div class="pull-left">
|
||||||
@avatar(latestCommit, 20)
|
@avatar(latestCommit, 20)
|
||||||
@user(latestCommit.authorName, latestCommit.authorEmailAddress, "username strong")
|
@user(latestCommit.authorName, latestCommit.authorEmailAddress, "username strong")
|
||||||
<span class="muted">@datetime(latestCommit.commitTime)</span>
|
<span class="muted">@helper.html.datetimeago(latestCommit.commitTime)</span>
|
||||||
<a href="@url(repository)/commit/@latestCommit.id" class="commit-message">@link(latestCommit.summary, repository)</a>
|
<a href="@url(repository)/commit/@latestCommit.id" class="commit-message">@link(latestCommit.summary, repository)</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="btn-group pull-right">
|
<div class="btn-group pull-right">
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
@datetime(latestUpdateDate)
|
@helper.html.datetimeago(latestUpdateDate, false)
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
@if(repository.repository.defaultBranch == branchName){
|
@if(repository.repository.defaultBranch == branchName){
|
||||||
|
|||||||
@@ -68,13 +68,13 @@
|
|||||||
<div class="author">
|
<div class="author">
|
||||||
@avatar(commit, 20)
|
@avatar(commit, 20)
|
||||||
<span>@user(commit.authorName, commit.authorEmailAddress, "username strong")</span>
|
<span>@user(commit.authorName, commit.authorEmailAddress, "username strong")</span>
|
||||||
<span class="muted">authored on @datetime(commit.authorTime)</span>
|
<span class="muted">authored @helper.html.datetimeago(commit.authorTime)</span>
|
||||||
</div>
|
</div>
|
||||||
@if(commit.isDifferentFromAuthor) {
|
@if(commit.isDifferentFromAuthor) {
|
||||||
<div class="committer">
|
<div class="committer">
|
||||||
<span class="icon-arrow-right"></span>
|
<span class="icon-arrow-right"></span>
|
||||||
<span>@user(commit.committerName, commit.committerEmailAddress, "username strong")</span>
|
<span>@user(commit.committerName, commit.committerEmailAddress, "username strong")</span>
|
||||||
<span class="muted"> committed on @datetime(commit.commitTime)</span>
|
<span class="muted"> committed @helper.html.datetimeago(commit.commitTime)</span>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,16 +3,17 @@
|
|||||||
repository: service.RepositoryService.RepositoryInfo,
|
repository: service.RepositoryService.RepositoryInfo,
|
||||||
commits: Seq[Seq[util.JGitUtil.CommitInfo]],
|
commits: Seq[Seq[util.JGitUtil.CommitInfo]],
|
||||||
page: Int,
|
page: Int,
|
||||||
hasNext: Boolean)(implicit context: app.Context)
|
hasNext: Boolean,
|
||||||
|
hasWritePermission: Boolean)(implicit context: app.Context)
|
||||||
@import context._
|
@import context._
|
||||||
@import view.helpers._
|
@import view.helpers._
|
||||||
@html.main(s"${repository.owner}/${repository.name}", Some(repository)) {
|
@html.main(s"${repository.owner}/${repository.name}", Some(repository)) {
|
||||||
@html.menu("code", repository){
|
@html.menu("code", repository){
|
||||||
<div class="head">
|
<div class="head">
|
||||||
@helper.html.dropdown(
|
@helper.html.branchcontrol(
|
||||||
value = if(branch.length == 40) branch.substring(0, 10) else branch,
|
branch,
|
||||||
prefix = if(branch.length == 40) "tree" else if(repository.branchList.contains(branch)) "branch" else "tree",
|
repository,
|
||||||
mini = true
|
hasWritePermission
|
||||||
){
|
){
|
||||||
@repository.branchList.map { x =>
|
@repository.branchList.map { x =>
|
||||||
<li><a href="@url(repository)/commits/@encodeRefName(x)">@helper.html.checkicon(x == branch) @x</a></li>
|
<li><a href="@url(repository)/commits/@encodeRefName(x)">@helper.html.checkicon(x == branch) @x</a></li>
|
||||||
@@ -58,11 +59,11 @@
|
|||||||
}
|
}
|
||||||
<div class="small">
|
<div class="small">
|
||||||
@user(commit.authorName, commit.authorEmailAddress, "username")
|
@user(commit.authorName, commit.authorEmailAddress, "username")
|
||||||
<span class="muted">authored @datetime(commit.authorTime)</span>
|
<span class="muted">authored @helper.html.datetimeago(commit.authorTime)</span>
|
||||||
@if(commit.isDifferentFromAuthor) {
|
@if(commit.isDifferentFromAuthor) {
|
||||||
<span class="icon-arrow-right" style="margin-top : -2px ;"></span>
|
<span class="icon-arrow-right" style="margin-top : -2px ;"></span>
|
||||||
@user(commit.committerName, commit.committerEmailAddress, "username")
|
@user(commit.committerName, commit.committerEmailAddress, "username")
|
||||||
<span class="muted">committed @datetime(commit.authorTime)</span>
|
<span class="muted">committed @helper.html.datetimeago(commit.authorTime)</span>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,16 +4,18 @@
|
|||||||
latestCommit: util.JGitUtil.CommitInfo,
|
latestCommit: util.JGitUtil.CommitInfo,
|
||||||
files: List[util.JGitUtil.FileInfo],
|
files: List[util.JGitUtil.FileInfo],
|
||||||
readme: Option[(List[String], String)],
|
readme: Option[(List[String], String)],
|
||||||
hasWritePermission: Boolean)(implicit context: app.Context)
|
hasWritePermission: Boolean,
|
||||||
|
info: Option[Any] = None,
|
||||||
|
error: Option[Any] = None)(implicit context: app.Context)
|
||||||
@import context._
|
@import context._
|
||||||
@import view.helpers._
|
@import view.helpers._
|
||||||
@html.main(s"${repository.owner}/${repository.name}", Some(repository)) {
|
@html.main(s"${repository.owner}/${repository.name}", Some(repository)) {
|
||||||
@html.menu("code", repository, Some(branch), pathList.isEmpty){
|
@html.menu("code", repository, Some(branch), pathList.isEmpty, info, error){
|
||||||
<div class="head">
|
<div class="head">
|
||||||
@helper.html.dropdown(
|
@helper.html.branchcontrol(
|
||||||
value = if(branch.length == 40) branch.substring(0, 10) else branch,
|
branch,
|
||||||
prefix = if(branch.length == 40) "tree" else if(repository.branchList.contains(branch)) "branch" else "tree",
|
repository,
|
||||||
mini = true
|
hasWritePermission
|
||||||
){
|
){
|
||||||
@repository.branchList.map { x =>
|
@repository.branchList.map { x =>
|
||||||
<li><a href="@url(repository)/tree/@encodeRefName(x)">@helper.html.checkicon(x == branch) @x</a></li>
|
<li><a href="@url(repository)/tree/@encodeRefName(x)">@helper.html.checkicon(x == branch) @x</a></li>
|
||||||
@@ -47,13 +49,13 @@
|
|||||||
<div class="author">
|
<div class="author">
|
||||||
@avatar(latestCommit, 20)
|
@avatar(latestCommit, 20)
|
||||||
<span>@user(latestCommit.authorName, latestCommit.authorEmailAddress, "username strong")</span>
|
<span>@user(latestCommit.authorName, latestCommit.authorEmailAddress, "username strong")</span>
|
||||||
<span class="muted"> authored on @datetime(latestCommit.authorTime)</span>
|
<span class="muted"> authored @helper.html.datetimeago(latestCommit.authorTime)</span>
|
||||||
</div>
|
</div>
|
||||||
@if(latestCommit.isDifferentFromAuthor) {
|
@if(latestCommit.isDifferentFromAuthor) {
|
||||||
<div class="committer">
|
<div class="committer">
|
||||||
<span class="icon-arrow-right"></span>
|
<span class="icon-arrow-right"></span>
|
||||||
<span>@user(latestCommit.committerName, latestCommit.committerEmailAddress, "username strong")</span>
|
<span>@user(latestCommit.committerName, latestCommit.committerEmailAddress, "username strong")</span>
|
||||||
<span class="muted"> committed on @datetime(latestCommit.commitTime)</span>
|
<span class="muted"> committed @helper.html.datetimeago(latestCommit.commitTime)</span>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@@ -106,7 +108,7 @@
|
|||||||
<a href="@url(repository)/commit/@file.commitId" class="commit-message">@link(file.message, repository)</a>
|
<a href="@url(repository)/commit/@file.commitId" class="commit-message">@link(file.message, repository)</a>
|
||||||
[@user(file.author, file.mailAddress)]
|
[@user(file.author, file.mailAddress)]
|
||||||
</td>
|
</td>
|
||||||
<td style="text-align: right;">@datetime(file.time)</td>
|
<td style="text-align: right;">@helper.html.datetimeago(file.time, false)</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
@repository.tags.reverse.map { tag =>
|
@repository.tags.reverse.map { tag =>
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="@url(repository)/tree/@encodeRefName(tag.name)">@tag.name</a></td>
|
<td><a href="@url(repository)/tree/@encodeRefName(tag.name)">@tag.name</a></td>
|
||||||
<td>@datetime(tag.time)</td>
|
<td>@helper.html.datetimeago(tag.time, false)</td>
|
||||||
<td class="monospace"><a href="@url(repository)/commit/@tag.id">@tag.id.substring(0, 10)</a></td>
|
<td class="monospace"><a href="@url(repository)/commit/@tag.id">@tag.id.substring(0, 10)</a></td>
|
||||||
<td>
|
<td>
|
||||||
<a href="@url(repository)/archive/@{encodeRefName(tag.name)}.zip">ZIP</a>
|
<a href="@url(repository)/archive/@{encodeRefName(tag.name)}.zip">ZIP</a>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
@files.drop((page - 1) * CodeLimit).take(CodeLimit).map { file =>
|
@files.drop((page - 1) * CodeLimit).take(CodeLimit).map { file =>
|
||||||
<div>
|
<div>
|
||||||
<h5><a href="@url(repository)/blob/@repository.repository.defaultBranch/@file.path">@file.path</a></h5>
|
<h5><a href="@url(repository)/blob/@repository.repository.defaultBranch/@file.path">@file.path</a></h5>
|
||||||
<div class="small muted">Latest commit at @datetime(file.lastModified)</div>
|
<div class="small muted">Last commited @helper.html.datetimeago(file.lastModified)</div>
|
||||||
<pre class="prettyprint linenums:@file.highlightLineNumber" style="padding-left: 25px;">@Html(file.highlightText)</pre>
|
<pre class="prettyprint linenums:@file.highlightLineNumber" style="padding-left: 25px;">@Html(file.highlightText)</pre>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
}
|
}
|
||||||
<div class="small muted">
|
<div class="small muted">
|
||||||
Opened by <a href="@url(issue.openedUserName)" class="username">@issue.openedUserName</a>
|
Opened by <a href="@url(issue.openedUserName)" class="username">@issue.openedUserName</a>
|
||||||
at @datetime(issue.registeredDate)
|
@helper.html.datetimeago(issue.registeredDate)
|
||||||
@if(issue.commentCount > 0){
|
@if(issue.commentCount > 0){
|
||||||
<i class="icon-comment"></i><span class="strong">@issue.commentCount</span> @plural(issue.commentCount, "comment")
|
<i class="icon-comment"></i><span class="strong">@issue.commentCount</span> @plural(issue.commentCount, "comment")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,19 +10,19 @@
|
|||||||
<h1 class="wiki-title"><span class="muted">Editing</span> @if(pageName.isEmpty){New Page} else {@pageName}</h1>
|
<h1 class="wiki-title"><span class="muted">Editing</span> @if(pageName.isEmpty){New Page} else {@pageName}</h1>
|
||||||
</li>
|
</li>
|
||||||
<li class="pull-right">
|
<li class="pull-right">
|
||||||
<div class="btn-group">
|
<div>
|
||||||
@if(page.isDefined){
|
@if(page.isDefined){
|
||||||
<a class="btn btn-small" href="@url(repository)/wiki/@urlEncode(pageName)">View Page</a>
|
|
||||||
<a class="btn btn-small" href="@url(repository)/wiki/@urlEncode(pageName)/_delete" id="delete">Delete Page</a>
|
<a class="btn btn-small" href="@url(repository)/wiki/@urlEncode(pageName)/_delete" id="delete">Delete Page</a>
|
||||||
<a class="btn btn-small" href="@url(repository)/wiki/@urlEncode(pageName)/_history">Page History</a>
|
<a class="btn btn-small" href="@url(repository)/wiki/@urlEncode(pageName)/_history">Page History</a>
|
||||||
}
|
}
|
||||||
|
<a class="btn btn-small btn-success" href="@url(repository)/wiki/_new">New Page</a>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<form action="@url(repository)/wiki/@if(page.isEmpty){_new} else {_edit}" method="POST" validate="true">
|
<form action="@url(repository)/wiki/@if(page.isEmpty){_new} else {_edit}" method="POST" validate="true">
|
||||||
<span id="error-pageName" class="error"></span>
|
<span id="error-pageName" class="error"></span>
|
||||||
<input type="text" name="pageName" value="@pageName" style="width: 850px; font-weight: bold;" placeholder="Input a page name."/>
|
<input type="text" name="pageName" value="@pageName" style="width: 850px; font-weight: bold;" placeholder="Input a page name."/>
|
||||||
@helper.html.preview(repository, page.map(_.content).getOrElse(""), true, false, "width: 850px; height: 400px;", "")
|
@helper.html.preview(repository, page.map(_.content).getOrElse(""), true, false, false, false, "width: 850px; height: 400px;", "")
|
||||||
<input type="text" name="message" value="" style="width: 850px;" placeholder="Write a small message here explaining this change. (Optional)"/>
|
<input type="text" name="message" value="" style="width: 850px;" placeholder="Write a small message here explaining this change. (Optional)"/>
|
||||||
<input type="hidden" name="currentPageName" value="@pageName"/>
|
<input type="hidden" name="currentPageName" value="@pageName"/>
|
||||||
<input type="hidden" name="id" value="@page.map(_.id)"/>
|
<input type="hidden" name="id" value="@page.map(_.id)"/>
|
||||||
|
|||||||
@@ -16,33 +16,39 @@
|
|||||||
</h1>
|
</h1>
|
||||||
</li>
|
</li>
|
||||||
<li class="pull-right">
|
<li class="pull-right">
|
||||||
<div class="btn-group">
|
<div>
|
||||||
@if(pageName.isEmpty){
|
@if(pageName.isEmpty){
|
||||||
@if(loginAccount.isDefined){
|
@if(loginAccount.isDefined){
|
||||||
<a class="btn btn-small" href="@url(repository)/wiki/_new">New Page</a>
|
<a class="btn btn-small" href="@url(repository)/wiki/_new">New Page</a>
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
<a class="btn btn-small" href="@url(repository)/wiki/@urlEncode(pageName)">View Page</a>
|
|
||||||
@if(loginAccount.isDefined){
|
@if(loginAccount.isDefined){
|
||||||
<a class="btn btn-small" href="@url(repository)/wiki/@urlEncode(pageName)/_edit">Edit Page</a>
|
<a class="btn btn-small" href="@url(repository)/wiki/@urlEncode(pageName)/_edit">Edit Page</a>
|
||||||
|
<a class="btn btn-small btn-success" href="@url(repository)/wiki/_new">New Page</a>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<table class="table table-bordered fill-width pull-left">
|
<table class="table table-bordered fill-width pull-left">
|
||||||
|
<tr>
|
||||||
|
<th colspan="3">
|
||||||
|
<div class="pull-left" style="padding-top: 4px;">Revisions</div>
|
||||||
|
<div class="pull-right">
|
||||||
|
<input type="button" id="compare" value="Compare Revisions" class="btn btn-mini"/>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
@commits.map { commit =>
|
@commits.map { commit =>
|
||||||
<tr>
|
<tr>
|
||||||
<td width="0%"><input type="checkbox" name="commitId" value="@commit.id"></td>
|
<td width="0%"><input type="checkbox" name="commitId" value="@commit.id"></td>
|
||||||
<td>@avatar(commit, 20) @user(commit.authorName, commit.authorEmailAddress)</td>
|
<td>@avatar(commit, 20) @user(commit.authorName, commit.authorEmailAddress)</td>
|
||||||
<td width="80%">
|
<td width="80%">
|
||||||
<span class="muted">@datetime(commit.authorTime):</span> @commit.shortMessage
|
<span class="muted">@helper.html.datetimeago(commit.authorTime):</span> @commit.shortMessage
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
</table>
|
</table>
|
||||||
<input type="button" id="compare" value="Compare Revisions" class="btn"/>
|
|
||||||
<input type="button" id="top" value="Back to Top" class="btn"/>
|
|
||||||
<script>
|
<script>
|
||||||
$(function(){
|
$(function(){
|
||||||
$('input[name=commitId]').click(function(){
|
$('input[name=commitId]').click(function(){
|
||||||
|
|||||||
@@ -12,17 +12,16 @@
|
|||||||
<li>
|
<li>
|
||||||
<h1 class="wiki-title">@pageName</h1>
|
<h1 class="wiki-title">@pageName</h1>
|
||||||
<div class="small">
|
<div class="small">
|
||||||
<span class="muted"><strong>@page.committer</strong> edited this page at @datetime(page.time)</span>
|
<span class="muted"><strong>@page.committer</strong> edited this page @helper.html.datetimeago(page.time)</span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li class="pull-right">
|
<li class="pull-right">
|
||||||
<div class="btn-group">
|
|
||||||
@if(hasWritePermission){
|
@if(hasWritePermission){
|
||||||
<a class="btn btn-small" href="@url(repository)/wiki/_new">New Page</a>
|
<div>
|
||||||
<a class="btn btn-small" href="@url(repository)/wiki/@urlEncode(pageName)/_edit">Edit Page</a>
|
<a class="btn btn-small" href="@url(repository)/wiki/@urlEncode(pageName)/_edit">Edit Page</a>
|
||||||
}
|
<a class="btn btn-small btn-success" href="@url(repository)/wiki/_new">New Page</a>
|
||||||
<a class="btn btn-small" href="@url(repository)/wiki/@urlEncode(pageName)/_history">Page History</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div style="width: 200px;" class="pull-right">
|
<div style="width: 200px;" class="pull-right">
|
||||||
|
|||||||
@@ -617,6 +617,30 @@ span.simplified-path {
|
|||||||
color: #888;
|
color: #888;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#branch-control-title {
|
||||||
|
margin: 5px 10px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
#branch-control-close {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: #aaa;
|
||||||
|
font-weight: bold;
|
||||||
|
line-height: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#branch-control-input {
|
||||||
|
border: solid 1px #ccc;
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-branch-name {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.2em;
|
||||||
|
padding-left: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
/****************************************************************************/
|
/****************************************************************************/
|
||||||
/* nav pulls group */
|
/* nav pulls group */
|
||||||
/****************************************************************************/
|
/****************************************************************************/
|
||||||
@@ -877,6 +901,20 @@ div.attachable div.clickable {
|
|||||||
background-color: white;
|
background-color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ul.task-list {
|
||||||
|
padding-left: 2em;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
li.task-list-item {
|
||||||
|
list-style-type: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
li.task-list-item input.task-list-item-checkbox {
|
||||||
|
margin: 0 4px 0.25em -20px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
/****************************************************************************/
|
/****************************************************************************/
|
||||||
/* Pull Request */
|
/* Pull Request */
|
||||||
/****************************************************************************/
|
/****************************************************************************/
|
||||||
|
|||||||
BIN
src/main/webapp/assets/common/images/menu-feed.png
Normal file
BIN
src/main/webapp/assets/common/images/menu-feed.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.3 KiB |
@@ -25,4 +25,69 @@ class GitBucketHtmlSerializerSpec extends Specification {
|
|||||||
after mustEqual "foo%21bar%40baz%3e9000"
|
after mustEqual "foo%21bar%40baz%3e9000"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"escapeTaskList" should {
|
||||||
|
"convert '- [ ] ' to '* task: :'" in {
|
||||||
|
val before = "- [ ] aaaa"
|
||||||
|
val after = escapeTaskList(before)
|
||||||
|
after mustEqual "* task: : aaaa"
|
||||||
|
}
|
||||||
|
|
||||||
|
"convert ' - [ ] ' to ' * task: :'" in {
|
||||||
|
val before = " - [ ] aaaa"
|
||||||
|
val after = escapeTaskList(before)
|
||||||
|
after mustEqual " * task: : aaaa"
|
||||||
|
}
|
||||||
|
|
||||||
|
"convert only first '- [ ] '" in {
|
||||||
|
val before = " - [ ] aaaa - [ ] bbb"
|
||||||
|
val after = escapeTaskList(before)
|
||||||
|
after mustEqual " * task: : aaaa - [ ] bbb"
|
||||||
|
}
|
||||||
|
|
||||||
|
"convert '- [x] ' to '* task:x:'" in {
|
||||||
|
val before = " - [x] aaaa"
|
||||||
|
val after = escapeTaskList(before)
|
||||||
|
after mustEqual " * task:x: aaaa"
|
||||||
|
}
|
||||||
|
|
||||||
|
"convert multi lines" in {
|
||||||
|
val before = """
|
||||||
|
tasks
|
||||||
|
- [x] aaaa
|
||||||
|
- [ ] bbb
|
||||||
|
"""
|
||||||
|
val after = escapeTaskList(before)
|
||||||
|
after mustEqual """
|
||||||
|
tasks
|
||||||
|
* task:x: aaaa
|
||||||
|
* task: : bbb
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
|
||||||
|
"no convert if inserted before '- [ ] '" in {
|
||||||
|
val before = " a - [ ] aaaa"
|
||||||
|
val after = escapeTaskList(before)
|
||||||
|
after mustEqual " a - [ ] aaaa"
|
||||||
|
}
|
||||||
|
|
||||||
|
"no convert '- [] '" in {
|
||||||
|
val before = " - [] aaaa"
|
||||||
|
val after = escapeTaskList(before)
|
||||||
|
after mustEqual " - [] aaaa"
|
||||||
|
}
|
||||||
|
|
||||||
|
"no convert '- [ ]a'" in {
|
||||||
|
val before = " - [ ]a aaaa"
|
||||||
|
val after = escapeTaskList(before)
|
||||||
|
after mustEqual " - [ ]a aaaa"
|
||||||
|
}
|
||||||
|
|
||||||
|
"no convert '-[ ] '" in {
|
||||||
|
val before = " -[ ] aaaa"
|
||||||
|
val after = escapeTaskList(before)
|
||||||
|
after mustEqual " -[ ] aaaa"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user