Merge branch 'new-issue-ui'
159
etc/icons.svg
@@ -34,9 +34,9 @@
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.7"
|
||||
inkscape:cx="482.58197"
|
||||
inkscape:cy="-83.92636"
|
||||
inkscape:zoom="0.98994949"
|
||||
inkscape:cx="174.78739"
|
||||
inkscape:cy="-195.96338"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1-9"
|
||||
showgrid="false"
|
||||
@@ -1583,7 +1583,7 @@
|
||||
<path
|
||||
id="path2991-7-1-4-1"
|
||||
transform="translate(-154.10522,1432.0357)"
|
||||
d="m 359.99999,290.93362 a 104.28571,104.28571 0 1 1 -208.57142,0 104.28571,104.28571 0 1 1 208.57142,0 z"
|
||||
d="m 359.99999,290.93362 c 0,57.59541 -46.6903,104.28572 -104.28571,104.28572 -57.59541,0 -104.28571,-46.69031 -104.28571,-104.28572 0,-57.5954 46.6903,-104.28571 104.28571,-104.28571 57.59541,0 104.28571,46.69031 104.28571,104.28571 z"
|
||||
sodipodi:ry="104.28571"
|
||||
sodipodi:rx="104.28571"
|
||||
sodipodi:cy="290.93362"
|
||||
@@ -1592,7 +1592,7 @@
|
||||
sodipodi:type="arc" />
|
||||
<path
|
||||
id="path2993-4-5-8-7"
|
||||
d="m 359.99999,290.93362 a 104.28571,104.28571 0 1 1 -208.57142,0 104.28571,104.28571 0 1 1 208.57142,0 z"
|
||||
d="m 359.99999,290.93362 c 0,57.59541 -46.6903,104.28572 -104.28571,104.28572 -57.59541,0 -104.28571,-46.69031 -104.28571,-104.28572 0,-57.5954 46.6903,-104.28571 104.28571,-104.28571 57.59541,0 104.28571,46.69031 104.28571,104.28571 z"
|
||||
sodipodi:ry="104.28571"
|
||||
sodipodi:rx="104.28571"
|
||||
sodipodi:cy="290.93362"
|
||||
@@ -1643,7 +1643,7 @@
|
||||
sodipodi:cy="812.36218"
|
||||
sodipodi:rx="10"
|
||||
sodipodi:ry="10"
|
||||
d="m 710,812.36218 a 10,10 0 1 1 -20,0 10,10 0 1 1 20,0 z"
|
||||
d="m 710,812.36218 c 0,5.52285 -4.47715,10 -10,10 -5.52285,0 -10,-4.47715 -10,-10 0,-5.52284 4.47715,-10 10,-10 5.52285,0 10,4.47716 10,10 z"
|
||||
transform="matrix(1.2362333,-1.2362333,1.2362333,1.2362333,-1490.7493,1534.7336)" />
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#bebeff;stroke-width:10.80681515000000000;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||
@@ -1670,7 +1670,7 @@
|
||||
style="fill:#ffffff;stroke:#bebefa;stroke-width:22.72570610000000000;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
transform="matrix(1.0049237,0,0,0.61497516,302.39116,1664.7945)"
|
||||
d="m 372.74629,230.89374 a 21.718279,35.140915 0 1 1 -43.43655,0 21.718279,35.140915 0 1 1 43.43655,0 z"
|
||||
d="m 372.74629,230.89374 c 0,19.40779 -9.7236,35.14091 -21.71827,35.14091 -11.99468,0 -21.71828,-15.73312 -21.71828,-35.14091 0,-19.40779 9.7236,-35.14092 21.71828,-35.14092 11.99467,0 21.71827,15.73313 21.71827,35.14092 z"
|
||||
sodipodi:ry="35.140915"
|
||||
sodipodi:rx="21.718279"
|
||||
sodipodi:cy="230.89374"
|
||||
@@ -1680,7 +1680,7 @@
|
||||
sodipodi:type="arc" />
|
||||
<path
|
||||
transform="matrix(1.0049237,0,0,0.61497516,300.85563,1514.4712)"
|
||||
d="m 372.74629,230.89374 a 21.718279,35.140915 0 1 1 -43.43655,0 21.718279,35.140915 0 1 1 43.43655,0 z"
|
||||
d="m 372.74629,230.89374 c 0,19.40779 -9.7236,35.14091 -21.71827,35.14091 -11.99468,0 -21.71828,-15.73312 -21.71828,-35.14091 0,-19.40779 9.7236,-35.14092 21.71828,-35.14092 11.99467,0 21.71827,15.73313 21.71827,35.14092 z"
|
||||
sodipodi:ry="35.140915"
|
||||
sodipodi:rx="21.718279"
|
||||
sodipodi:cy="230.89374"
|
||||
@@ -1690,7 +1690,7 @@
|
||||
sodipodi:type="arc" />
|
||||
<path
|
||||
transform="matrix(1.0049237,0,0,0.61497516,401.70879,1561.5007)"
|
||||
d="m 372.74629,230.89374 a 21.718279,35.140915 0 1 1 -43.43655,0 21.718279,35.140915 0 1 1 43.43655,0 z"
|
||||
d="m 372.74629,230.89374 c 0,19.40779 -9.7236,35.14091 -21.71827,35.14091 -11.99468,0 -21.71828,-15.73312 -21.71828,-35.14091 0,-19.40779 9.7236,-35.14092 21.71828,-35.14092 11.99467,0 21.71827,15.73313 21.71827,35.14092 z"
|
||||
sodipodi:ry="35.140915"
|
||||
sodipodi:rx="21.718279"
|
||||
sodipodi:cy="230.89374"
|
||||
@@ -1698,6 +1698,147 @@
|
||||
id="path3795-8-4-8-2-1"
|
||||
style="fill:#ffffff;stroke:#bebeff;stroke-width:12.04511166000000000;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
sodipodi:type="arc" />
|
||||
<g
|
||||
id="g3992">
|
||||
<rect
|
||||
transform="matrix(0.70710678,0.70710678,-0.70710678,0.70710678,0,0)"
|
||||
style="fill:#3c3c3c;fill-opacity:1;stroke:none"
|
||||
width="34.635483"
|
||||
height="158.96587"
|
||||
x="1836.6243"
|
||||
y="-1788.4895"
|
||||
id="rect2995-0-8-4-1" />
|
||||
<rect
|
||||
transform="matrix(0.70710678,-0.70710678,0.70710678,0.70710678,0,0)"
|
||||
style="fill:#3c3c3c;fill-opacity:1;stroke:none"
|
||||
width="33.538391"
|
||||
height="96.944809"
|
||||
x="1628.6003"
|
||||
y="1772.8655"
|
||||
id="rect2995-0-8-4-1-4" />
|
||||
</g>
|
||||
<g
|
||||
id="g4112"
|
||||
transform="translate(88.611046,-13.773858)">
|
||||
<rect
|
||||
transform="matrix(0.70710678,0.70710678,-0.70710678,0.70710678,0,0)"
|
||||
style="fill:#a0a0a0;fill-opacity:1;stroke:none"
|
||||
width="34.635483"
|
||||
height="158.96587"
|
||||
x="1527.2657"
|
||||
y="-1466.7803"
|
||||
id="rect2995-0-8-4-1-5" />
|
||||
<rect
|
||||
transform="matrix(0.70710678,-0.70710678,0.70710678,0.70710678,0,0)"
|
||||
style="fill:#a0a0a0;fill-opacity:1;stroke:none"
|
||||
width="33.538391"
|
||||
height="96.944809"
|
||||
x="1306.8911"
|
||||
y="1463.507"
|
||||
id="rect2995-0-8-4-1-4-5" />
|
||||
</g>
|
||||
<path
|
||||
style="fill:#b3b3b3;stroke:none"
|
||||
d="m 2185.2705,373.3859 -109.47,85.45235 29.4727,-89.94984 z"
|
||||
id="path3894-1-1"
|
||||
inkscape:connector-curvature="0" />
|
||||
<rect
|
||||
style="fill:#b3b3b3;stroke:#b3b3b3;stroke-width:49.97417831;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0"
|
||||
id="rect3088-5-5-7"
|
||||
width="174.36192"
|
||||
height="89.170021"
|
||||
x="2060.0393"
|
||||
y="293.00055" />
|
||||
<rect
|
||||
style="fill:#dcdcdc;fill-opacity:1;stroke:#dcdcdc;stroke-width:2.10925268999999990;stroke-linejoin:miter;stroke-miterlimit:4.30000019000000040;stroke-opacity:1;stroke-dasharray:none"
|
||||
id="rect4170"
|
||||
width="35.913948"
|
||||
height="206.36755"
|
||||
x="2110.2112"
|
||||
y="507.8555" />
|
||||
<rect
|
||||
style="fill:#dcdcdc;fill-opacity:1;stroke:#ffffff;stroke-width:15.12008381000000100;stroke-linejoin:miter;stroke-miterlimit:4.30000019000000040;stroke-opacity:1;stroke-dasharray:none"
|
||||
id="rect4166"
|
||||
width="174.5864"
|
||||
height="76.446434"
|
||||
x="2035.1414"
|
||||
y="548.66016" />
|
||||
<rect
|
||||
style="fill:#dcdcdc;fill-opacity:1;stroke:#dcdcdc;stroke-width:1.42725468000000010;stroke-linejoin:miter;stroke-miterlimit:4.30000019000000040;stroke-opacity:1;stroke-dasharray:none"
|
||||
id="rect4174"
|
||||
width="43.442127"
|
||||
height="43.442127"
|
||||
x="1928.0846"
|
||||
y="-1122.7543"
|
||||
transform="matrix(0.72181305,0.69208809,-0.72181305,0.69208809,0,0)" />
|
||||
<path
|
||||
sodipodi:type="arc"
|
||||
style="fill:#ffffe6;fill-opacity:1;stroke:#ffffff;stroke-width:10.1960001;stroke-linejoin:miter;stroke-miterlimit:4.30000019;stroke-opacity:1;stroke-dasharray:none"
|
||||
id="path4364"
|
||||
sodipodi:cx="1418.2542"
|
||||
sodipodi:cy="434.14883"
|
||||
sodipodi:rx="11.111678"
|
||||
sodipodi:ry="11.111678"
|
||||
d="m 1429.3658,434.14883 c 0,6.13681 -4.9748,11.11168 -11.1116,11.11168 -6.1369,0 -11.1117,-4.97487 -11.1117,-11.11168 0,-6.13681 4.9748,-11.11167 11.1117,-11.11167 6.1368,0 11.1116,4.97486 11.1116,11.11167 z"
|
||||
transform="matrix(1.2783369,0,0,1.2783369,315.0834,31.171302)" />
|
||||
<path
|
||||
style="fill:#dcdcdc;stroke:none;fill-opacity:1"
|
||||
d="m 2533.6893,373.6989 -109.47,85.45235 29.4727,-89.94984 z"
|
||||
id="path3894-1-1-1"
|
||||
inkscape:connector-curvature="0" />
|
||||
<rect
|
||||
style="fill:#dcdcdc;stroke:#dcdcdc;stroke-width:49.97417449999999700;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;fill-opacity:1"
|
||||
id="rect3088-5-5-7-7"
|
||||
width="174.36192"
|
||||
height="89.170021"
|
||||
x="2408.458"
|
||||
y="293.31354" />
|
||||
<rect
|
||||
style="fill:#3c3c3c;fill-opacity:1;stroke:#888888;stroke-width:73.08132935000000400;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||
id="rect3220"
|
||||
width="104.54597"
|
||||
height="104.54597"
|
||||
x="45.94949"
|
||||
y="1925.303" />
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none"
|
||||
id="rect3998-1"
|
||||
width="117.84303"
|
||||
height="30.608574"
|
||||
x="1271.0641"
|
||||
y="-1484.6459"
|
||||
transform="matrix(-0.70710678,0.70710678,-0.70710678,-0.70710678,0,0)" />
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none"
|
||||
id="rect3998-1-7"
|
||||
width="117.84303"
|
||||
height="30.608574"
|
||||
x="1408.8896"
|
||||
y="1314.712"
|
||||
transform="matrix(0.70710678,0.70710678,-0.70710678,0.70710678,0,0)" />
|
||||
<rect
|
||||
style="fill:#0088cc;fill-opacity:1;stroke:#0088cc;stroke-width:73.08132935000000400;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||
id="rect3220-4"
|
||||
width="104.54597"
|
||||
height="104.54597"
|
||||
x="337.49615"
|
||||
y="1924.5376" />
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none"
|
||||
id="rect3998-1-0"
|
||||
width="117.84303"
|
||||
height="30.608574"
|
||||
x="1064.3683"
|
||||
y="-1690.2594"
|
||||
transform="matrix(-0.70710678,0.70710678,-0.70710678,-0.70710678,0,0)" />
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none"
|
||||
id="rect3998-1-7-9"
|
||||
width="117.84303"
|
||||
height="30.608574"
|
||||
x="1614.5032"
|
||||
y="1108.0162"
|
||||
transform="matrix(0.70710678,0.70710678,-0.70710678,0.70710678,0,0)" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 93 KiB |
@@ -50,20 +50,20 @@ trait DashboardControllerBase extends ControllerBase {
|
||||
|
||||
val userName = context.loginAccount.get.userName
|
||||
val userRepos = getUserRepositories(userName, context.baseUrl, true).map(repo => repo.owner -> repo.name)
|
||||
val filterUser = Map(filter -> userName)
|
||||
//val filterUser = Map(filter -> userName)
|
||||
val page = IssueSearchCondition.page(request)
|
||||
|
||||
dashboard.html.issues(
|
||||
issues.html.listparts(
|
||||
searchIssue(condition, filterUser, false, (page - 1) * IssueLimit, IssueLimit, userRepos: _*),
|
||||
dashboard.html.issueslist(
|
||||
searchIssue(condition, false, (page - 1) * IssueLimit, IssueLimit, userRepos: _*),
|
||||
page,
|
||||
countIssue(condition.copy(state = "open" ), filterUser, false, userRepos: _*),
|
||||
countIssue(condition.copy(state = "closed"), filterUser, false, userRepos: _*),
|
||||
countIssue(condition.copy(state = "open" ), false, userRepos: _*),
|
||||
countIssue(condition.copy(state = "closed"), false, userRepos: _*),
|
||||
condition),
|
||||
countIssue(condition, Map.empty, false, userRepos: _*),
|
||||
countIssue(condition, Map("assigned" -> userName), false, userRepos: _*),
|
||||
countIssue(condition, Map("created_by" -> userName), false, userRepos: _*),
|
||||
countIssueGroupByRepository(condition, filterUser, false, userRepos: _*),
|
||||
countIssue(condition.copy(assigned = None, author = None), false, userRepos: _*),
|
||||
countIssue(condition.copy(assigned = Some(userName), author = None), false, userRepos: _*),
|
||||
countIssue(condition.copy(assigned = None, author = Some(userName)), false, userRepos: _*),
|
||||
countIssueGroupByRepository(condition, false, userRepos: _*),
|
||||
condition,
|
||||
filter)
|
||||
|
||||
@@ -86,14 +86,14 @@ trait DashboardControllerBase extends ControllerBase {
|
||||
val page = IssueSearchCondition.page(request)
|
||||
|
||||
val counts = countIssueGroupByRepository(
|
||||
IssueSearchCondition().copy(state = condition.state), Map.empty, true, userRepos: _*)
|
||||
IssueSearchCondition().copy(state = condition.state), true, userRepos: _*)
|
||||
|
||||
dashboard.html.pulls(
|
||||
pulls.html.listparts(
|
||||
searchIssue(condition, filterUser, true, (page - 1) * PullRequestLimit, PullRequestLimit, allRepos: _*),
|
||||
dashboard.html.pullslist(
|
||||
searchIssue(condition, true, (page - 1) * PullRequestLimit, PullRequestLimit, allRepos: _*),
|
||||
page,
|
||||
countIssue(condition.copy(state = "open" ), filterUser, true, allRepos: _*),
|
||||
countIssue(condition.copy(state = "closed"), filterUser, true, allRepos: _*),
|
||||
countIssue(condition.copy(state = "open" ), true, allRepos: _*),
|
||||
countIssue(condition.copy(state = "closed"), true, allRepos: _*),
|
||||
condition,
|
||||
None,
|
||||
false),
|
||||
|
||||
@@ -21,7 +21,6 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
|
||||
case class IssueCreateForm(title: String, content: Option[String],
|
||||
assignedUserName: Option[String], milestoneId: Option[Int], labelNames: Option[String])
|
||||
case class IssueEditForm(title: String, content: Option[String])
|
||||
case class CommentForm(issueId: Int, content: String)
|
||||
case class IssueStateForm(issueId: Int, content: Option[String])
|
||||
|
||||
@@ -33,10 +32,12 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
"labelNames" -> trim(optional(text()))
|
||||
)(IssueCreateForm.apply)
|
||||
|
||||
val issueTitleEditForm = mapping(
|
||||
"title" -> trim(label("Title", text(required)))
|
||||
)(x => x)
|
||||
val issueEditForm = mapping(
|
||||
"title" -> trim(label("Title", text(required))),
|
||||
"content" -> trim(optional(text()))
|
||||
)(IssueEditForm.apply)
|
||||
)(x => x)
|
||||
|
||||
val commentForm = mapping(
|
||||
"issueId" -> label("Issue Id", number()),
|
||||
@@ -48,16 +49,8 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
"content" -> trim(optional(text()))
|
||||
)(IssueStateForm.apply)
|
||||
|
||||
get("/:owner/:repository/issues")(referrersOnly {
|
||||
searchIssues("all", _)
|
||||
})
|
||||
|
||||
get("/:owner/:repository/issues/assigned/:userName")(referrersOnly {
|
||||
searchIssues("assigned", _)
|
||||
})
|
||||
|
||||
get("/:owner/:repository/issues/created_by/:userName")(referrersOnly {
|
||||
searchIssues("created_by", _)
|
||||
get("/:owner/:repository/issues")(referrersOnly { repository =>
|
||||
searchIssues(repository)
|
||||
})
|
||||
|
||||
get("/:owner/:repository/issues/:id")(referrersOnly { repository =>
|
||||
@@ -126,14 +119,29 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
}
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/edit/:id", issueEditForm)(readableUsersOnly { (form, repository) =>
|
||||
ajaxPost("/:owner/:repository/issues/edit_title/:id", issueTitleEditForm)(readableUsersOnly { (title, repository) =>
|
||||
defining(repository.owner, repository.name){ case (owner, name) =>
|
||||
getIssue(owner, name, params("id")).map { issue =>
|
||||
if(isEditable(owner, name, issue.openedUserName)){
|
||||
// update issue
|
||||
updateIssue(owner, name, issue.issueId, form.title, form.content)
|
||||
updateIssue(owner, name, issue.issueId, title, issue.content)
|
||||
// extract references and create refer comment
|
||||
createReferComment(owner, name, issue, form.title + " " + form.content.getOrElse(""))
|
||||
createReferComment(owner, name, issue.copy(title = title), title)
|
||||
|
||||
redirect(s"/${owner}/${name}/issues/_data/${issue.issueId}")
|
||||
} else Unauthorized
|
||||
} getOrElse NotFound
|
||||
}
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/edit/:id", issueEditForm)(readableUsersOnly { (content, repository) =>
|
||||
defining(repository.owner, repository.name){ case (owner, name) =>
|
||||
getIssue(owner, name, params("id")).map { issue =>
|
||||
if(isEditable(owner, name, issue.openedUserName)){
|
||||
// update issue
|
||||
updateIssue(owner, name, issue.issueId, issue.title, content)
|
||||
// extract references and create refer comment
|
||||
createReferComment(owner, name, issue, content.getOrElse(""))
|
||||
|
||||
redirect(s"/${owner}/${name}/issues/_data/${issue.issueId}")
|
||||
} else Unauthorized
|
||||
@@ -181,7 +189,7 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
if(isEditable(x.userName, x.repositoryName, x.openedUserName)){
|
||||
params.get("dataType") collect {
|
||||
case t if t == "html" => issues.html.editissue(
|
||||
x.title, x.content, x.issueId, x.userName, x.repositoryName)
|
||||
x.content, x.issueId, x.userName, x.repositoryName)
|
||||
} getOrElse {
|
||||
contentType = formats("json")
|
||||
org.json4s.jackson.Serialization.write(
|
||||
@@ -235,15 +243,17 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
milestoneId("milestoneId").map { milestoneId =>
|
||||
getMilestonesWithIssueCount(repository.owner, repository.name)
|
||||
.find(_._1.milestoneId == milestoneId).map { case (_, openCount, closeCount) =>
|
||||
issues.milestones.html.progress(openCount + closeCount, closeCount, false)
|
||||
issues.milestones.html.progress(openCount + closeCount, closeCount)
|
||||
} getOrElse NotFound
|
||||
} getOrElse Ok()
|
||||
})
|
||||
|
||||
post("/:owner/:repository/issues/batchedit/state")(collaboratorsOnly { repository =>
|
||||
defining(params.get("value")){ action =>
|
||||
executeBatch(repository) {
|
||||
handleComment(_, None, repository)( _ => action)
|
||||
action match {
|
||||
case Some("open") => executeBatch(repository) { handleComment(_, None, repository)( _ => Some("reopen")) }
|
||||
case Some("close") => executeBatch(repository) { handleComment(_, None, repository)( _ => Some("close")) }
|
||||
case _ => // TODO BadRequest
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -293,7 +303,10 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
|
||||
private def executeBatch(repository: RepositoryService.RepositoryInfo)(execute: Int => Unit) = {
|
||||
params("checked").split(',') map(_.toInt) foreach execute
|
||||
redirect(s"/${repository.owner}/${repository.name}/issues")
|
||||
params("from") match {
|
||||
case "issues" => redirect(s"/${repository.owner}/${repository.name}/issues")
|
||||
case "pulls" => redirect(s"/${repository.owner}/${repository.name}/pulls")
|
||||
}
|
||||
}
|
||||
|
||||
private def createReferComment(owner: String, repository: String, fromIssue: Issue, message: String) = {
|
||||
@@ -319,10 +332,10 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
val (action, recordActivity) =
|
||||
getAction(issue)
|
||||
.collect {
|
||||
case "close" => true -> (Some("close") ->
|
||||
Some(if(issue.isPullRequest) recordClosePullRequestActivity _ else recordCloseIssueActivity _))
|
||||
case "reopen" => false -> (Some("reopen") ->
|
||||
Some(recordReopenIssueActivity _))
|
||||
case "close" if(!issue.closed) => true ->
|
||||
(Some("close") -> Some(if(issue.isPullRequest) recordClosePullRequestActivity _ else recordCloseIssueActivity _))
|
||||
case "reopen" if(issue.closed) => false ->
|
||||
(Some("reopen") -> Some(recordReopenIssueActivity _))
|
||||
}
|
||||
.map { case (closed, t) =>
|
||||
updateClosed(owner, name, issueId, closed)
|
||||
@@ -337,7 +350,7 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
case (content, action) => createComment(owner, name, userName, issueId, content, action)
|
||||
}
|
||||
|
||||
// record activity
|
||||
// record comment activity if comment is entered
|
||||
content foreach {
|
||||
(if(issue.isPullRequest) recordCommentPullRequestActivity _ else recordCommentIssueActivity _)
|
||||
(owner, name, userName, issueId, _)
|
||||
@@ -370,9 +383,8 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
}
|
||||
}
|
||||
|
||||
private def searchIssues(filter: String, repository: RepositoryService.RepositoryInfo) = {
|
||||
private def searchIssues(repository: RepositoryService.RepositoryInfo) = {
|
||||
defining(repository.owner, repository.name){ case (owner, repoName) =>
|
||||
val filterUser = Map(filter -> params.getOrElse("userName", ""))
|
||||
val page = IssueSearchCondition.page(request)
|
||||
val sessionKey = Keys.Session.Issues(owner, repoName)
|
||||
|
||||
@@ -383,19 +395,15 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
)
|
||||
|
||||
issues.html.list(
|
||||
searchIssue(condition, filterUser, false, (page - 1) * IssueLimit, IssueLimit, owner -> repoName),
|
||||
"issues",
|
||||
searchIssue(condition, false, (page - 1) * IssueLimit, IssueLimit, owner -> repoName),
|
||||
page,
|
||||
(getCollaborators(owner, repoName) :+ owner).sorted,
|
||||
getMilestones(owner, repoName),
|
||||
getLabels(owner, repoName),
|
||||
countIssue(condition.copy(state = "open"), filterUser, false, owner -> repoName),
|
||||
countIssue(condition.copy(state = "closed"), filterUser, false, owner -> repoName),
|
||||
countIssue(condition, Map.empty, false, owner -> repoName),
|
||||
context.loginAccount.map(x => countIssue(condition, Map("assigned" -> x.userName), false, owner -> repoName)),
|
||||
context.loginAccount.map(x => countIssue(condition, Map("created_by" -> x.userName), false, owner -> repoName)),
|
||||
countIssueGroupByLabels(owner, repoName, condition, filterUser),
|
||||
countIssue(condition.copy(state = "open" ), false, owner -> repoName),
|
||||
countIssue(condition.copy(state = "closed"), false, owner -> repoName),
|
||||
condition,
|
||||
filter,
|
||||
repository,
|
||||
hasWritePermission(owner, repoName, context.loginAccount))
|
||||
}
|
||||
|
||||
@@ -2,51 +2,67 @@ package app
|
||||
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
import service._
|
||||
import util.CollaboratorsAuthenticator
|
||||
import util.{ReferrerAuthenticator, CollaboratorsAuthenticator}
|
||||
import util.Implicits._
|
||||
import org.scalatra.i18n.Messages
|
||||
import org.scalatra.Ok
|
||||
|
||||
class LabelsController extends LabelsControllerBase
|
||||
with LabelsService with RepositoryService with AccountService with CollaboratorsAuthenticator
|
||||
with LabelsService with IssuesService with RepositoryService with AccountService
|
||||
with ReferrerAuthenticator with CollaboratorsAuthenticator
|
||||
|
||||
trait LabelsControllerBase extends ControllerBase {
|
||||
self: LabelsService with RepositoryService with CollaboratorsAuthenticator =>
|
||||
self: LabelsService with IssuesService with RepositoryService
|
||||
with ReferrerAuthenticator with CollaboratorsAuthenticator =>
|
||||
|
||||
case class LabelForm(labelName: String, color: String)
|
||||
|
||||
val newForm = mapping(
|
||||
"newLabelName" -> trim(label("Label name", text(required, labelName, maxlength(100)))),
|
||||
"newColor" -> trim(label("Color", text(required, color)))
|
||||
val labelForm = mapping(
|
||||
"labelName" -> trim(label("Label name", text(required, labelName, maxlength(100)))),
|
||||
"labelColor" -> trim(label("Color", text(required, color)))
|
||||
)(LabelForm.apply)
|
||||
|
||||
val editForm = mapping(
|
||||
"editLabelName" -> trim(label("Label name", text(required, labelName, maxlength(100)))),
|
||||
"editColor" -> trim(label("Color", text(required, color)))
|
||||
)(LabelForm.apply)
|
||||
|
||||
post("/:owner/:repository/issues/label/new", newForm)(collaboratorsOnly { (form, repository) =>
|
||||
createLabel(repository.owner, repository.name, form.labelName, form.color.substring(1))
|
||||
redirect(s"/${repository.owner}/${repository.name}/issues")
|
||||
get("/:owner/:repository/issues/labels")(referrersOnly { repository =>
|
||||
issues.labels.html.list(
|
||||
getLabels(repository.owner, repository.name),
|
||||
countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition()),
|
||||
repository,
|
||||
hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
||||
})
|
||||
|
||||
ajaxGet("/:owner/:repository/issues/label/edit")(collaboratorsOnly { repository =>
|
||||
issues.labels.html.editlist(getLabels(repository.owner, repository.name), repository)
|
||||
ajaxGet("/:owner/:repository/issues/labels/new")(collaboratorsOnly { repository =>
|
||||
issues.labels.html.edit(None, repository)
|
||||
})
|
||||
|
||||
ajaxGet("/:owner/:repository/issues/label/:labelId/edit")(collaboratorsOnly { repository =>
|
||||
ajaxPost("/:owner/:repository/issues/labels/new", labelForm)(collaboratorsOnly { (form, repository) =>
|
||||
val labelId = createLabel(repository.owner, repository.name, form.labelName, form.color.substring(1))
|
||||
issues.labels.html.label(
|
||||
getLabel(repository.owner, repository.name, labelId).get,
|
||||
// TODO futility
|
||||
countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition()),
|
||||
repository,
|
||||
hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
||||
})
|
||||
|
||||
ajaxGet("/:owner/:repository/issues/labels/:labelId/edit")(collaboratorsOnly { repository =>
|
||||
getLabel(repository.owner, repository.name, params("labelId").toInt).map { label =>
|
||||
issues.labels.html.edit(Some(label), repository)
|
||||
} getOrElse NotFound()
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/label/:labelId/edit", editForm)(collaboratorsOnly { (form, repository) =>
|
||||
ajaxPost("/:owner/:repository/issues/labels/:labelId/edit", labelForm)(collaboratorsOnly { (form, repository) =>
|
||||
updateLabel(repository.owner, repository.name, params("labelId").toInt, form.labelName, form.color.substring(1))
|
||||
issues.labels.html.editlist(getLabels(repository.owner, repository.name), repository)
|
||||
issues.labels.html.label(
|
||||
getLabel(repository.owner, repository.name, params("labelId").toInt).get,
|
||||
// TODO futility
|
||||
countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition()),
|
||||
repository,
|
||||
hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
||||
})
|
||||
|
||||
ajaxGet("/:owner/:repository/issues/label/:labelId/delete")(collaboratorsOnly { repository =>
|
||||
ajaxPost("/:owner/:repository/issues/labels/:labelId/delete")(collaboratorsOnly { repository =>
|
||||
deleteLabel(repository.owner, repository.name, params("labelId").toInt)
|
||||
issues.labels.html.editlist(getLabels(repository.owner, repository.name), repository)
|
||||
Ok()
|
||||
})
|
||||
|
||||
/**
|
||||
|
||||
@@ -62,10 +62,6 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
searchPullRequests(None, repository)
|
||||
})
|
||||
|
||||
get("/:owner/:repository/pulls/:userName")(referrersOnly { repository =>
|
||||
searchPullRequests(Some(params("userName")), repository)
|
||||
})
|
||||
|
||||
get("/:owner/:repository/pull/:id")(referrersOnly { repository =>
|
||||
params("id").toIntOpt.flatMap{ issueId =>
|
||||
val owner = repository.owner
|
||||
@@ -453,7 +449,6 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
|
||||
private def searchPullRequests(userName: Option[String], repository: RepositoryService.RepositoryInfo) =
|
||||
defining(repository.owner, repository.name){ case (owner, repoName) =>
|
||||
val filterUser = userName.map { x => Map("created_by" -> x) } getOrElse Map("all" -> "")
|
||||
val page = IssueSearchCondition.page(request)
|
||||
val sessionKey = Keys.Session.Pulls(owner, repoName)
|
||||
|
||||
@@ -463,14 +458,15 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
else session.getAs[IssueSearchCondition](sessionKey).getOrElse(IssueSearchCondition())
|
||||
)
|
||||
|
||||
pulls.html.list(
|
||||
searchIssue(condition, filterUser, true, (page - 1) * PullRequestLimit, PullRequestLimit, owner -> repoName),
|
||||
getPullRequestCountGroupByUser(condition.state == "closed", Some(owner), Some(repoName)),
|
||||
userName,
|
||||
issues.html.list(
|
||||
"pulls",
|
||||
searchIssue(condition, true, (page - 1) * PullRequestLimit, PullRequestLimit, owner -> repoName),
|
||||
page,
|
||||
countIssue(condition.copy(state = "open" ), filterUser, true, owner -> repoName),
|
||||
countIssue(condition.copy(state = "closed"), filterUser, true, owner -> repoName),
|
||||
countIssue(condition, Map.empty, true, owner -> repoName),
|
||||
(getCollaborators(owner, repoName) :+ owner).sorted,
|
||||
getMilestones(owner, repoName),
|
||||
getLabels(owner, repoName),
|
||||
countIssue(condition.copy(state = "open" ), true, owner -> repoName),
|
||||
countIssue(condition.copy(state = "closed"), true, owner -> repoName),
|
||||
condition,
|
||||
repository,
|
||||
hasWritePermission(owner, repoName, context.loginAccount))
|
||||
|
||||
@@ -31,7 +31,7 @@ case class Label(
|
||||
if(Integer.parseInt(r, 16) + Integer.parseInt(g, 16) + Integer.parseInt(b, 16) > 408){
|
||||
"000000"
|
||||
} else {
|
||||
"FFFFFF"
|
||||
"ffffff"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,14 +43,13 @@ trait IssuesService {
|
||||
* Returns the count of the search result against issues.
|
||||
*
|
||||
* @param condition the search condition
|
||||
* @param filterUser the filter user name (key is "all", "assigned" or "created_by", value is the user name)
|
||||
* @param onlyPullRequest if true then counts only pull request, false then counts both of issue and pull request.
|
||||
* @param repos Tuple of the repository owner and the repository name
|
||||
* @return the count of the search result
|
||||
*/
|
||||
def countIssue(condition: IssueSearchCondition, filterUser: Map[String, String], onlyPullRequest: Boolean,
|
||||
repos: (String, String)*)(implicit s: Session): Int =
|
||||
Query(searchIssueQuery(repos, condition, filterUser, onlyPullRequest).length).first
|
||||
def countIssue(condition: IssueSearchCondition, onlyPullRequest: Boolean, repos: (String, String)*)
|
||||
(implicit s: Session): Int =
|
||||
Query(searchIssueQuery(repos, condition, onlyPullRequest).length).first
|
||||
|
||||
/**
|
||||
* Returns the Map which contains issue count for each labels.
|
||||
@@ -58,13 +57,12 @@ trait IssuesService {
|
||||
* @param owner the repository owner
|
||||
* @param repository the repository name
|
||||
* @param condition the search condition
|
||||
* @param filterUser the filter user name (key is "all", "assigned" or "created_by", value is the user name)
|
||||
* @return the Map which contains issue count for each labels (key is label name, value is issue count)
|
||||
*/
|
||||
def countIssueGroupByLabels(owner: String, repository: String, condition: IssueSearchCondition,
|
||||
filterUser: Map[String, String])(implicit s: Session): Map[String, Int] = {
|
||||
def countIssueGroupByLabels(owner: String, repository: String,
|
||||
condition: IssueSearchCondition)(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) =>
|
||||
t1.byIssue(t2.userName, t2.repositoryName, t2.issueId)
|
||||
}
|
||||
@@ -84,15 +82,14 @@ trait IssuesService {
|
||||
* If the issue does not exist, its repository is not included in the result.
|
||||
*
|
||||
* @param condition the search condition
|
||||
* @param filterUser the filter user name (key is "all", "assigned" or "created_by", value is the user name)
|
||||
* @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,
|
||||
condition: IssueSearchCondition, onlyPullRequest: Boolean,
|
||||
repos: (String, String)*)(implicit s: Session): List[(String, String, Int)] = {
|
||||
searchIssueQuery(repos, condition.copy(repo = None), filterUser, onlyPullRequest)
|
||||
searchIssueQuery(repos, condition.copy(repo = None), onlyPullRequest)
|
||||
.groupBy { t =>
|
||||
t.userName -> t.repositoryName
|
||||
}
|
||||
@@ -107,19 +104,18 @@ trait IssuesService {
|
||||
* Returns the search result against issues.
|
||||
*
|
||||
* @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 onlyPullRequest if true then returns only pull request, false then returns both of issue and pull request.
|
||||
* @param pullRequest if true then returns only pull requests, false then returns only issues.
|
||||
* @param offset the offset for pagination
|
||||
* @param limit the limit for pagination
|
||||
* @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)
|
||||
*/
|
||||
def searchIssue(condition: IssueSearchCondition, filterUser: Map[String, String], onlyPullRequest: Boolean,
|
||||
def searchIssue(condition: IssueSearchCondition, pullRequest: Boolean,
|
||||
offset: Int, limit: Int, repos: (String, String)*)
|
||||
(implicit s: Session): List[(Issue, List[Label], Int)] = {
|
||||
(implicit s: Session): List[IssueInfo] = {
|
||||
|
||||
// get issues and comment count and labels
|
||||
searchIssueQuery(repos, condition, filterUser, onlyPullRequest)
|
||||
searchIssueQuery(repos, condition, pullRequest)
|
||||
.innerJoin(IssueOutline).on { (t1, t2) => t1.byIssue(t2.userName, t2.repositoryName, t2.issueId) }
|
||||
.sortBy { case (t1, t2) =>
|
||||
(condition.sort match {
|
||||
@@ -136,8 +132,9 @@ trait IssuesService {
|
||||
.drop(offset).take(limit)
|
||||
.leftJoin (IssueLabels) .on { case ((t1, t2), t3) => t1.byIssue(t3.userName, t3.repositoryName, t3.issueId) }
|
||||
.leftJoin (Labels) .on { case (((t1, t2), t3), t4) => t3.byLabel(t4.userName, t4.repositoryName, t4.labelId) }
|
||||
.map { case (((t1, t2), t3), t4) =>
|
||||
(t1, t2.commentCount, t4.labelId.?, t4.labelName.?, t4.color.?)
|
||||
.leftJoin (Milestones) .on { case ((((t1, t2), t3), t4), t5) => t1.byMilestone(t5.userName, t5.repositoryName, t5.milestoneId) }
|
||||
.map { case ((((t1, t2), t3), t4), t5) =>
|
||||
(t1, t2.commentCount, t4.labelId.?, t4.labelName.?, t4.color.?, t5.title.?)
|
||||
}
|
||||
.list
|
||||
.splitWith { (c1, c2) =>
|
||||
@@ -146,11 +143,12 @@ trait IssuesService {
|
||||
c1._1.issueId == c2._1.issueId
|
||||
}
|
||||
.map { issues => issues.head match {
|
||||
case (issue, commentCount, _,_,_) =>
|
||||
(issue,
|
||||
case (issue, commentCount, _, _, _, milestone) =>
|
||||
IssueInfo(issue,
|
||||
issues.flatMap { t => t._3.map (
|
||||
Label(issue.userName, issue.repositoryName, _, t._4.get, t._5.get)
|
||||
)} toList,
|
||||
milestone,
|
||||
commentCount)
|
||||
}} toList
|
||||
}
|
||||
@@ -159,7 +157,7 @@ trait IssuesService {
|
||||
* Assembles query for conditional issue searching.
|
||||
*/
|
||||
private def searchIssueQuery(repos: Seq[(String, String)], condition: IssueSearchCondition,
|
||||
filterUser: Map[String, String], onlyPullRequest: Boolean)(implicit s: Session) =
|
||||
pullRequest: Boolean)(implicit s: Session) =
|
||||
Issues filter { t1 =>
|
||||
condition.repo
|
||||
.map { _.split('/') match { case array => Seq(array(0) -> array(1)) } }
|
||||
@@ -169,10 +167,9 @@ trait IssuesService {
|
||||
(t1.closed === (condition.state == "closed").bind) &&
|
||||
(t1.milestoneId === condition.milestoneId.get.get.bind, condition.milestoneId.flatten.isDefined) &&
|
||||
(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.pullRequest === true.bind, onlyPullRequest) &&
|
||||
(t1.assignedUserName === condition.assigned.get.bind, condition.assigned.isDefined) &&
|
||||
(t1.openedUserName === condition.author.get.bind, condition.author.isDefined) &&
|
||||
(t1.pullRequest === pullRequest.bind) &&
|
||||
(IssueLabels filter { t2 =>
|
||||
(t2.byIssue(t1.userName, t1.repositoryName, t1.issueId)) &&
|
||||
(t2.labelId in
|
||||
@@ -337,11 +334,20 @@ object IssuesService {
|
||||
case class IssueSearchCondition(
|
||||
labels: Set[String] = Set.empty,
|
||||
milestoneId: Option[Option[Int]] = None,
|
||||
author: Option[String] = None,
|
||||
assigned: Option[String] = None,
|
||||
repo: Option[String] = None,
|
||||
state: String = "open",
|
||||
sort: String = "created",
|
||||
direction: String = "desc"){
|
||||
|
||||
def isEmpty: Boolean = {
|
||||
labels.isEmpty && milestoneId.isEmpty && author.isEmpty && assigned.isEmpty &&
|
||||
state == "open" && sort == "created" && direction == "desc"
|
||||
}
|
||||
|
||||
def nonEmpty: Boolean = !isEmpty
|
||||
|
||||
def toURL: String =
|
||||
"?" + List(
|
||||
if(labels.isEmpty) None else Some("labels=" + urlEncode(labels.mkString(","))),
|
||||
@@ -349,6 +355,8 @@ object IssuesService {
|
||||
case Some(x) => x.toString
|
||||
case None => "none"
|
||||
})},
|
||||
author .map(x => "author=" + urlEncode(x)),
|
||||
assigned.map(x => "assigned=" + urlEncode(x)),
|
||||
repo.map("for=" + urlEncode(_)),
|
||||
Some("state=" + urlEncode(state)),
|
||||
Some("sort=" + urlEncode(sort)),
|
||||
@@ -370,6 +378,8 @@ object IssuesService {
|
||||
case "none" => None
|
||||
case x => x.toIntOpt
|
||||
},
|
||||
param(request, "author"),
|
||||
param(request, "assigned"),
|
||||
param(request, "for"),
|
||||
param(request, "state", Seq("open", "closed")).getOrElse("open"),
|
||||
param(request, "sort", Seq("created", "comments", "updated")).getOrElse("created"),
|
||||
@@ -383,4 +393,6 @@ object IssuesService {
|
||||
}
|
||||
}
|
||||
|
||||
case class IssueInfo(issue: Issue, labels: List[Label], milestone: Option[String], commentCount: Int)
|
||||
|
||||
}
|
||||
|
||||
@@ -12,8 +12,8 @@ trait LabelsService {
|
||||
def getLabel(owner: String, repository: String, labelId: Int)(implicit s: Session): Option[Label] =
|
||||
Labels.filter(_.byPrimaryKey(owner, repository, labelId)).firstOption
|
||||
|
||||
def createLabel(owner: String, repository: String, labelName: String, color: String)(implicit s: Session): Unit =
|
||||
Labels insert Label(
|
||||
def createLabel(owner: String, repository: String, labelName: String, color: String)(implicit s: Session): Int =
|
||||
Labels returning Labels.map(_.labelId) += Label(
|
||||
userName = owner,
|
||||
repositoryName = repository,
|
||||
labelName = labelName,
|
||||
|
||||
@@ -134,8 +134,8 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
|
||||
// Retrieve all issue count in the repository
|
||||
val issueCount =
|
||||
countIssue(IssueSearchCondition(state = "open"), Map.empty, false, owner -> repository) +
|
||||
countIssue(IssueSearchCondition(state = "closed"), Map.empty, false, owner -> repository)
|
||||
countIssue(IssueSearchCondition(state = "open"), false, owner -> repository) +
|
||||
countIssue(IssueSearchCondition(state = "closed"), false, owner -> repository)
|
||||
|
||||
// Extract new commit and apply issue comment
|
||||
val defaultBranch = getRepository(owner, repository, baseUrl).get.repository.defaultBranch
|
||||
|
||||
184
src/main/twirl/dashboard/issueslist.scala.html
Normal file
@@ -0,0 +1,184 @@
|
||||
@(issues: List[service.IssuesService.IssueInfo],
|
||||
page: Int,
|
||||
openCount: Int,
|
||||
closedCount: Int,
|
||||
condition: service.IssuesService.IssueSearchCondition,
|
||||
collaborators: List[String] = Nil,
|
||||
milestones: List[model.Milestone] = Nil,
|
||||
labels: List[model.Label] = Nil,
|
||||
repository: Option[service.RepositoryService.RepositoryInfo] = None,
|
||||
hasWritePermission: Boolean = false)(implicit context: app.Context)
|
||||
@import context._
|
||||
@import view.helpers._
|
||||
@import service.IssuesService.IssueInfo
|
||||
<div class="span9">
|
||||
@if(condition.labels.nonEmpty || condition.milestoneId.isDefined){
|
||||
<a href="@condition.copy(labels = Set.empty, milestoneId = None).toURL" id="clear-filter">
|
||||
<i class="icon-remove-circle"></i> Clear milestone and label filters
|
||||
</a>
|
||||
}
|
||||
@if(condition.repo.isDefined){
|
||||
<a href="@condition.copy(repo = None).toURL" id="clear-filter">
|
||||
<i class="icon-remove-circle"></i> Clear filter on @condition.repo
|
||||
</a>
|
||||
}
|
||||
<div class="pull-right">
|
||||
@helper.html.paginator(page, (if(condition.state == "open") openCount else closedCount), service.IssuesService.IssueLimit, 7, condition.toURL)
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<a class="btn 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>
|
||||
<td style="padding: 20px; background-color: #eee; text-align: center;">
|
||||
No issues to show.
|
||||
@if(condition.labels.nonEmpty || condition.milestoneId.isDefined){
|
||||
<a href="@condition.copy(labels = Set.empty, milestoneId = None).toURL">Clear active filters.</a>
|
||||
} else {
|
||||
@if(repository.isDefined){
|
||||
<a href="@url(repository.get)/issues/new">Create a new issue.</a>
|
||||
}
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
} else {
|
||||
@if(hasWritePermission){
|
||||
<tr>
|
||||
<td style="background-color: #eee;">
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-mini 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) =>
|
||||
<tr>
|
||||
<td>
|
||||
@if(hasWritePermission){
|
||||
<label class="checkbox" style="cursor: default;">
|
||||
<input type="checkbox" value="@issue.issueId"/>
|
||||
}
|
||||
@if(issue.isPullRequest){
|
||||
<img src="@assets/common/images/pullreq-@(if(issue.closed) "closed" else "open").png"/>
|
||||
} else {
|
||||
<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> ・
|
||||
}
|
||||
@if(issue.isPullRequest){
|
||||
<a href="@path/@issue.userName/@issue.repositoryName/pull/@issue.issueId" class="issue-title">@issue.title</a>
|
||||
} else {
|
||||
<a href="@path/@issue.userName/@issue.repositoryName/issues/@issue.issueId" class="issue-title">@issue.title</a>
|
||||
}
|
||||
@labels.map { label =>
|
||||
<span class="label-color small" style="background-color: #@label.color; color: #@label.fontColor; padding-left: 4px; padding-right: 4px">@label.labelName</span>
|
||||
}
|
||||
<span class="pull-right muted">
|
||||
@issue.assignedUserName.map { userName =>
|
||||
@avatar(userName, 20, tooltip = true)
|
||||
}
|
||||
#@issue.issueId
|
||||
</span>
|
||||
<div class="small muted" style="margin-left: 20px;">
|
||||
Opened by @user(issue.openedUserName, styleClass="username") @datetime(issue.registeredDate)
|
||||
@if(commentCount > 0){
|
||||
<i class="icon-comment"></i><a href="@path/@issue.userName/@issue.repositoryName/issues/@issue.issueId" class="issue-comment-count">@commentCount @plural(commentCount, "comment")</a>
|
||||
}
|
||||
</div>
|
||||
@if(hasWritePermission){
|
||||
</label>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</table>
|
||||
<div class="pull-right">
|
||||
@helper.html.paginator(page, (if(condition.state == "open") openCount else closedCount), service.IssuesService.IssueLimit, 10, condition.toURL)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@(issues: List[(model.Issue, List[model.Label], Int)],
|
||||
@(issues: List[service.IssuesService.IssueInfo],
|
||||
page: Int,
|
||||
openCount: Int,
|
||||
closedCount: Int,
|
||||
@@ -7,6 +7,7 @@
|
||||
hasWritePermission: Boolean)(implicit context: app.Context)
|
||||
@import context._
|
||||
@import view.helpers._
|
||||
@import service.IssuesService.IssueInfo
|
||||
<div class="span9">
|
||||
@repository.map { repository =>
|
||||
@if(hasWritePermission){
|
||||
@@ -71,7 +72,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@issues.map { case (issue, labels, commentCount) =>
|
||||
@issues.map { case IssueInfo(issue, labels, milestone, commentCount) =>
|
||||
<tr>
|
||||
<td>
|
||||
<img src="@assets/common/images/pullreq-@(if(issue.closed) "closed" else "open").png"/>
|
||||
@@ -1,6 +1,13 @@
|
||||
@(value: String = "", prefix: String = "", mini: Boolean = true, style: String = "", right: Boolean = false)(body: Html)
|
||||
@(value : String = "",
|
||||
prefix: String = "",
|
||||
mini : Boolean = true,
|
||||
style : String = "",
|
||||
right : Boolean = false,
|
||||
flat : Boolean = false)(body: Html)
|
||||
<div class="btn-group" @if(style.nonEmpty){style="@style"}>
|
||||
<button class="btn dropdown-toggle@if(mini){ btn-mini} else { btn-small}" data-toggle="dropdown">
|
||||
<button
|
||||
@if(flat){style="border: none; background-color: #eee;"}
|
||||
class="dropdown-toggle @if(!flat){btn} else {flat} @if(mini){btn-mini} else {btn-small}" data-toggle="dropdown">
|
||||
@if(value.isEmpty){
|
||||
<i class="icon-cog"></i>
|
||||
} else {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
@import context._
|
||||
@import view.helpers._
|
||||
@if(loginAccount.isDefined){
|
||||
<hr/><br/>
|
||||
<form method="POST" validate="true">
|
||||
<div class="issue-avatar-image">@avatar(loginAccount.get.userName, 48)</div>
|
||||
<div class="box issue-comment-box">
|
||||
|
||||
@@ -5,20 +5,36 @@
|
||||
pullreq: Option[model.PullRequest] = None)(implicit context: app.Context)
|
||||
@import context._
|
||||
@import view.helpers._
|
||||
<div class="issue-avatar-image">@avatar(issue.openedUserName, 48)</div>
|
||||
<div class="box issue-comment-box">
|
||||
<div class="box-header-small">
|
||||
@user(issue.openedUserName, styleClass="username strong") <span class="muted">commented on @datetime(issue.registeredDate)</span>
|
||||
<span class="pull-right">
|
||||
@if(hasWritePermission || loginAccount.map(_.userName == issue.openedUserName).getOrElse(false)){
|
||||
<a href="#" data-issue-id="@issue.issueId"><i class="icon-pencil"></i></a>
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
<div class="box-content issue-content" id="issueContent">
|
||||
@markdown(issue.content getOrElse "No description provided.", repository, false, true)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@comments.map { comment =>
|
||||
@if(comment.action != "close" && comment.action != "reopen" && comment.action != "delete_branch"){
|
||||
<div class="issue-avatar-image">@avatar(comment.commentedUserName, 48)</div>
|
||||
<div class="box issue-comment-box" id="comment-@comment.commentId">
|
||||
<div class="box-header-small">
|
||||
<i class="icon-comment"></i>
|
||||
@user(comment.commentedUserName, styleClass="username strong")
|
||||
<span class="muted">
|
||||
@if(comment.action == "comment"){
|
||||
commented
|
||||
} else {
|
||||
@if(pullreq.isEmpty){ referenced the issue } else { referenced the pull request }
|
||||
}
|
||||
on @datetime(comment.registeredDate)
|
||||
</span>
|
||||
<span class="pull-right">
|
||||
@datetime(comment.registeredDate)
|
||||
@if(comment.action != "commit" && comment.action != "merge" && comment.action != "refer" &&
|
||||
(hasWritePermission || loginAccount.map(_.userName == comment.commentedUserName).getOrElse(false))){
|
||||
<a href="#" data-comment-id="@comment.commentId"><i class="icon-pencil"></i></a>
|
||||
@@ -87,12 +103,21 @@
|
||||
$(function(){
|
||||
$('i.icon-pencil').click(function(){
|
||||
var id = $(this).closest('a').data('comment-id');
|
||||
$.get('@url(repository)/issue_comments/_data/' + id,
|
||||
var url = '@url(repository)/issue_comments/_data/' + id;
|
||||
var $content = $('#commentContent-' + id);
|
||||
|
||||
if(!id){
|
||||
id = $(this).closest('a').data('issue-id');
|
||||
url = '@url(repository)/issues/_data/' + id;
|
||||
$content = $('#issueContent');
|
||||
}
|
||||
|
||||
$.get(url,
|
||||
{
|
||||
dataType : 'html'
|
||||
},
|
||||
function(data){
|
||||
$('#commentContent-' + id).empty().html(data);
|
||||
$content.empty().html(data);
|
||||
});
|
||||
return false;
|
||||
});
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
@import view.helpers._
|
||||
@html.main(s"New Issue - ${repository.owner}/${repository.name}", Some(repository)){
|
||||
@html.menu("issues", repository){
|
||||
@tab("", true, repository)
|
||||
@tab("issues", false, repository)
|
||||
<br/><br/><hr style="margin-bottom: 10px;">
|
||||
<form action="@url(repository)/issues/new" method="POST" validate="true">
|
||||
<div class="row-fluid">
|
||||
<div class="span9">
|
||||
@@ -32,7 +33,7 @@
|
||||
@if(hasWritePermission){
|
||||
<input type="hidden" name="milestoneId" value=""/>
|
||||
@helper.html.dropdown() {
|
||||
<li><a href="javascript:void(0);" class="milestone" data-id=""><i class="icon-remove-circle"></i> No milestone</a></li>
|
||||
<li><a href="javascript:void(0);" class="milestone" data-id=""><i class="icon-remove-circle"></i> Clear this milestone</a></li>
|
||||
@milestones.filter(_.closedDate.isEmpty).map { milestone =>
|
||||
<li>
|
||||
<a href="javascript:void(0);" class="milestone" data-id="@milestone.milestoneId" data-title="@milestone.title">
|
||||
@@ -40,9 +41,9 @@
|
||||
<div class="small" style="padding-left: 20px;">
|
||||
@milestone.dueDate.map { dueDate =>
|
||||
@if(isPast(dueDate)){
|
||||
<img src="@assets/common/images/alert_mono.png"/>Due in @date(dueDate)
|
||||
<img src="@assets/common/images/alert.png"/><span class="milestone-alert">Due by @date(dueDate)</span>
|
||||
} else {
|
||||
<span class="muted">Due in @date(dueDate)</span>
|
||||
<span class="muted">Due by @date(dueDate)</span>
|
||||
}
|
||||
}.getOrElse {
|
||||
<span class="muted">No due date</span>
|
||||
@@ -65,7 +66,7 @@
|
||||
</div>
|
||||
<div class="span3">
|
||||
@if(hasWritePermission){
|
||||
<span class="strong">Add Labels</span>
|
||||
<span class="strong">Labels</span>
|
||||
<div>
|
||||
<div id="label-list">
|
||||
<ul class="label-list nav nav-pills nav-stacked">
|
||||
@@ -112,7 +113,7 @@ $(function(){
|
||||
if(milestoneId == ''){
|
||||
$('#label-milestone').text('No milestone');
|
||||
} else {
|
||||
$('#label-milestone').html($('<span>').append('Milestone: ').append($('<span class="strong">').text(title)));
|
||||
$('#label-milestone').html($('<span class="strong">').text(title));
|
||||
$('a.milestone[data-id=' + milestoneId + '] i').attr('class', 'icon-ok');
|
||||
}
|
||||
$('input[name=milestoneId]').val(milestoneId);
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
<textarea style="width: 635px; height: 100px;" id="edit-content-@commentId">@content</textarea>
|
||||
}
|
||||
<div>
|
||||
<input type="button" id="update-comment-@commentId" class="btn btn-small" value="Update Comment"/>
|
||||
<input type="button" id="cancel-comment-@commentId" class="btn btn-small btn-danger pull-right" value="Cancel"/>
|
||||
<input type="button" id="cancel-comment-@commentId" class="btn btn-small btn-danger" value="Cancel"/>
|
||||
<input type="button" id="update-comment-@commentId" class="btn btn-small pull-right" value="Update comment"/>
|
||||
</div>
|
||||
<script>
|
||||
$(function(){
|
||||
|
||||
@@ -1,42 +1,35 @@
|
||||
@(title: String, content: Option[String], issueId: Int, owner: String, repository: String)(implicit context: app.Context)
|
||||
@(content: Option[String], issueId: Int, owner: String, repository: String)(implicit context: app.Context)
|
||||
@import context._
|
||||
<span id="error-edit-title" class="error"></span>
|
||||
<input type="text" style="width: 635px;" id="edit-title" value="@title"/>
|
||||
@helper.html.attached(owner, repository){
|
||||
<textarea style="width: 635px; height: 100px; max-height: 300px;" id="edit-content">@content.getOrElse("")</textarea>
|
||||
}
|
||||
<div>
|
||||
<input type="button" id="update" class="btn btn-small" value="Update Issue"/>
|
||||
<input type="button" id="cancel" class="btn btn-small btn-danger pull-right" value="Cancel"/>
|
||||
<input type="button" id="cancel-issue" class="btn btn-small btn-danger" value="Cancel"/>
|
||||
<input type="button" id="update-issue" class="btn btn-small pull-right" value="Update comment"/>
|
||||
</div>
|
||||
<script>
|
||||
$(function(){
|
||||
$('#edit-content').elastic();
|
||||
|
||||
var callback = function(data){
|
||||
$('#update, #cancel').removeAttr('disabled');
|
||||
$('#issueTitle').empty().text(data.title);
|
||||
$('#issueContent').empty().html(data.content);
|
||||
};
|
||||
|
||||
$('#update').click(function(){
|
||||
$('#update-issue').click(function(){
|
||||
$('#update, #cancel').attr('disabled', 'disabled');
|
||||
$.ajax({
|
||||
url: '@path/@owner/@repository/issues/edit/@issueId',
|
||||
type: 'POST',
|
||||
data: {
|
||||
title : $('#edit-title').val(),
|
||||
content : $('#edit-content').val()
|
||||
}
|
||||
}).done(
|
||||
callback
|
||||
).fail(function(req) {
|
||||
$('#update, #cancel').removeAttr('disabled');
|
||||
$('#error-edit-title').text($.parseJSON(req.responseText).title);
|
||||
});
|
||||
});
|
||||
|
||||
$('#cancel').click(function(){
|
||||
$('#cancel-issue').click(function(){
|
||||
$('#update, #cancel').attr('disabled', 'disabled');
|
||||
$.get('@path/@owner/@repository/issues/_data/@issueId', callback);
|
||||
return false;
|
||||
|
||||
@@ -10,31 +10,86 @@
|
||||
@import view.helpers._
|
||||
@html.main(s"${issue.title} - Issue #${issue.issueId} - ${repository.owner}/${repository.name}", Some(repository)){
|
||||
@html.menu("issues", repository){
|
||||
@tab("issues", false, repository)
|
||||
<ul class="nav nav-tabs pull-left fill-width">
|
||||
<li class="pull-left"><a href="@url(repository)/issues"><i class="icon-arrow-left"></i> Back to issue list</a></li>
|
||||
<li class="pull-right">Issue #@issue.issueId</li>
|
||||
</ul>
|
||||
<div class="row-fluid">
|
||||
<div class="span10">
|
||||
@issuedetail(issue, comments, collaborators, milestones, hasWritePermission, repository)
|
||||
@commentlist(issue, comments, hasWritePermission, repository)
|
||||
@commentform(issue, true, hasWritePermission, repository)
|
||||
</div>
|
||||
<div class="span2">
|
||||
<li class="pull-left">
|
||||
<h1>
|
||||
<span class="show-title">
|
||||
<span id="show-title">@issue.title</span>
|
||||
<span class="muted">#@issue.issueId</span>
|
||||
</span>
|
||||
<span class="edit-title" style="display: none;">
|
||||
<span id="error-edit-title" class="error"></span>
|
||||
<input type="text" class="span9" id="edit-title" value="@issue.title"/>
|
||||
</span>
|
||||
</h1>
|
||||
@if(issue.closed) {
|
||||
<span class="label label-important issue-status">Closed</span>
|
||||
} else {
|
||||
<span class="label label-success issue-status">Open</span>
|
||||
}
|
||||
<div class="small" style="text-align: center;">
|
||||
@defining(comments.filter( _.action.contains("comment") ).size){ count =>
|
||||
<span class="strong">@count</span> @plural(count, "comment")
|
||||
<span class="muted">
|
||||
@user(issue.openedUserName, styleClass="username strong") opened this issue on @datetime(issue.registeredDate) - @defining(
|
||||
comments.filter( _.action.contains("comment") ).size
|
||||
){ count =>
|
||||
@count @plural(count, "comment")
|
||||
}
|
||||
</span>
|
||||
<br/><br/>
|
||||
</li>
|
||||
<li class="pull-right">
|
||||
<div class="show-title">
|
||||
@if(hasWritePermission || loginAccount.map(_.userName == issue.openedUserName).getOrElse(false)){
|
||||
<a class="btn btn-small" href="#" id="edit">Edit</a>
|
||||
}
|
||||
<a class="btn btn-small btn-success" href="@url(repository)/issues/new">New issue</a>
|
||||
</div>
|
||||
<hr/>
|
||||
@issues.html.labels(issue, issueLabels, labels, hasWritePermission, repository)
|
||||
<div class="edit-title" style="display: none;">
|
||||
<a class="btn" href="#" id="update">Save</a> <a href="#" id="cancel">Cancel</a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="row-fluid">
|
||||
<div class="span10">
|
||||
@commentlist(issue, comments, hasWritePermission, repository)
|
||||
@commentform(issue, true, hasWritePermission, repository)
|
||||
</div>
|
||||
<div class="span2">
|
||||
@issueinfo(issue, comments, issueLabels, collaborators, milestones, labels, hasWritePermission, repository)
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
<script>
|
||||
$(function(){
|
||||
$('#edit').click(function(){
|
||||
$('.edit-title').show();
|
||||
$('.show-title').hide();
|
||||
return false;
|
||||
});
|
||||
|
||||
$('#update').click(function(){
|
||||
$(this).attr('disabled', 'disabled');
|
||||
$.ajax({
|
||||
url: '@url(repository)/issues/edit_title/@issue.issueId',
|
||||
type: 'POST',
|
||||
data: {
|
||||
title : $('#edit-title').val()
|
||||
}
|
||||
}).done(function(data){
|
||||
$('#show-title').empty().text(data.title);
|
||||
$('#cancel').click();
|
||||
$(this).removeAttr('disabled');
|
||||
}).fail(function(req){
|
||||
$(this).removeAttr('disabled');
|
||||
$('#error-edit-title').text($.parseJSON(req.responseText).title);
|
||||
});
|
||||
return false;
|
||||
});
|
||||
|
||||
$('#cancel').click(function(){
|
||||
$('.edit-title').hide();
|
||||
$('.show-title').show();
|
||||
return false;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -1,145 +0,0 @@
|
||||
@(issue: model.Issue,
|
||||
comments: List[model.IssueComment],
|
||||
collaborators: List[String],
|
||||
milestones: List[(model.Milestone, Int, Int)],
|
||||
hasWritePermission: Boolean,
|
||||
repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context)
|
||||
@import context._
|
||||
@import view.helpers._
|
||||
<div class="issue-avatar-image">@avatar(issue.openedUserName, 48)</div>
|
||||
<div class="box issue-box">
|
||||
<div class="box-content" style="padding: 0px;">
|
||||
<div class="issue-header">
|
||||
@if(hasWritePermission || loginAccount.map(_.userName == issue.openedUserName).getOrElse(false)){
|
||||
<span class="pull-right"><a class="btn btn-small" href="#" id="edit">Edit</a></span>
|
||||
}
|
||||
<div class="small muted">
|
||||
@user(issue.openedUserName, styleClass="username strong") opened this issue @datetime(issue.registeredDate)
|
||||
</div>
|
||||
<h4 id="issueTitle">@issue.title</h4>
|
||||
</div>
|
||||
<div class="issue-info">
|
||||
<span id="label-assigned">
|
||||
@issue.assignedUserName.map { userName =>
|
||||
@avatar(userName, 20) @user(userName, styleClass="username strong") is assigned
|
||||
}.getOrElse("No one is assigned")
|
||||
</span>
|
||||
@if(hasWritePermission){
|
||||
@helper.html.dropdown() {
|
||||
<li><a href="javascript:void(0);" class="assign" data-name=""><i class="icon-remove-circle"></i> Clear assignee</a></li>
|
||||
@collaborators.map { collaborator =>
|
||||
<li>
|
||||
<a href="javascript:void(0);" class="assign" data-name="@collaborator">
|
||||
@helper.html.checkicon(Some(collaborator) == issue.assignedUserName)@avatar(collaborator, 20) @collaborator
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
}
|
||||
}
|
||||
<div class="pull-right">
|
||||
<span id="label-milestone">
|
||||
@issue.milestoneId.map { milestoneId =>
|
||||
@milestones.collect { case (milestone, _, _) if(milestone.milestoneId == milestoneId) =>
|
||||
Milestone: <span class="strong">@milestone.title</span>
|
||||
}
|
||||
}.getOrElse("No milestone")
|
||||
</span>
|
||||
<div id="milestone-progress-area">
|
||||
@issue.milestoneId.map { milestoneId =>
|
||||
@milestones.collect { case (milestone, openCount, closeCount) if(milestone.milestoneId == milestoneId) =>
|
||||
@issues.milestones.html.progress(openCount + closeCount, closeCount, false)
|
||||
}
|
||||
}
|
||||
</div>
|
||||
@if(hasWritePermission){
|
||||
@helper.html.dropdown() {
|
||||
<li><a href="javascript:void(0);" class="milestone" data-id=""><i class="icon-remove-circle"></i> Clear this milestone</a></li>
|
||||
@milestones.filter(_._1.closedDate.isEmpty).map { case (milestone, _, _) =>
|
||||
<li>
|
||||
<a href="javascript:void(0);" class="milestone" data-id="@milestone.milestoneId" data-title="@milestone.title">
|
||||
@helper.html.checkicon(Some(milestone.milestoneId) == issue.milestoneId) @milestone.title
|
||||
<div class="small" style="padding-left: 20px;">
|
||||
@milestone.dueDate.map { dueDate =>
|
||||
@if(isPast(dueDate)){
|
||||
<img src="@assets/common/images/alert_mono.png"/>Due in @date(dueDate)
|
||||
} else {
|
||||
<span class="muted">Due in @date(dueDate)</span>
|
||||
}
|
||||
}.getOrElse {
|
||||
<span class="muted">No due date</span>
|
||||
}
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="issue-content" id="issueContent">
|
||||
@markdown(issue.content getOrElse "No description given.", repository, false, true)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="issue-participants">
|
||||
@defining((issue.openedUserName :: comments.map(_.commentedUserName)).distinct){ participants =>
|
||||
<span class="strong">@participants.size</span> @plural(participants.size, "participant")
|
||||
@participants.map { participant => @avatarLink(participant, 20, tooltip = true) }
|
||||
}
|
||||
</div>
|
||||
<script>
|
||||
$(function(){
|
||||
$('#edit').click(function(){
|
||||
$.get('@url(repository)/issues/_data/@issue.issueId',
|
||||
{
|
||||
dataType : 'html'
|
||||
},
|
||||
function(data){
|
||||
$('#issueContent').empty().html(data);
|
||||
});
|
||||
return false;
|
||||
});
|
||||
|
||||
$('a.assign').click(function(){
|
||||
var $this = $(this);
|
||||
var userName = $this.data('name');
|
||||
$.post('@url(repository)/issues/@issue.issueId/assign',
|
||||
{
|
||||
assignedUserName: userName
|
||||
},
|
||||
function(){
|
||||
$('a.assign i.icon-ok').attr('class', 'icon-white');
|
||||
if(userName == ''){
|
||||
$('#label-assigned').text('No one is assigned');
|
||||
} else {
|
||||
$('#label-assigned').empty()
|
||||
.append($this.find('img.avatar').clone(false)).append(' ')
|
||||
.append($('<a class="username strong">').attr('href', '@path/' + userName).text(userName))
|
||||
.append(' is assigned');
|
||||
$('a.assign[data-name=' + jqSelectorEscape(userName) + '] i').attr('class', 'icon-ok');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$('a.milestone').click(function(){
|
||||
var title = $(this).data('title');
|
||||
var milestoneId = $(this).data('id');
|
||||
$.post('@url(repository)/issues/@issue.issueId/milestone',
|
||||
{
|
||||
milestoneId: milestoneId
|
||||
},
|
||||
function(data){
|
||||
console.log(data);
|
||||
$('a.milestone i.icon-ok').attr('class', 'icon-white');
|
||||
if(milestoneId == ''){
|
||||
$('#label-milestone').text('No milestone');
|
||||
$('#milestone-progress-area').empty();
|
||||
} else {
|
||||
$('#label-milestone').html($('<span>').append('Milestone: ').append($('<span class="strong">').text(title)));
|
||||
$('#milestone-progress-area').html(data);
|
||||
$('a.milestone[data-id=' + milestoneId + '] i').attr('class', 'icon-ok');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
169
src/main/twirl/issues/issueinfo.scala.html
Normal file
@@ -0,0 +1,169 @@
|
||||
@(issue: model.Issue,
|
||||
comments: List[model.IssueComment],
|
||||
issueLabels: List[model.Label],
|
||||
collaborators: List[String],
|
||||
milestones: List[(model.Milestone, Int, Int)],
|
||||
labels: List[model.Label],
|
||||
hasWritePermission: Boolean,
|
||||
repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context)
|
||||
@import view.helpers._
|
||||
<div style="margin-bottom: 8px;">
|
||||
<span class="muted small strong">Labels</span>
|
||||
@if(hasWritePermission){
|
||||
<div class="pull-right">
|
||||
@helper.html.dropdown(right = true) {
|
||||
@labels.map { label =>
|
||||
<li>
|
||||
<a href="#" class="toggle-label" data-label-id="@label.labelId">
|
||||
@helper.html.checkicon(issueLabels.exists(_.labelId == label.labelId))
|
||||
<span class="label" style="background-color: #@label.color;"> </span>
|
||||
@label.labelName
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<ul class="label-list nav nav-pills nav-stacked">
|
||||
@labellist(issueLabels)
|
||||
</ul>
|
||||
<hr/>
|
||||
<div style="margin-bottom: 8px;">
|
||||
<span class="muted small strong">Milestone</span>
|
||||
@if(hasWritePermission){
|
||||
<div class="pull-right">
|
||||
@helper.html.dropdown() {
|
||||
<li><a href="javascript:void(0);" class="milestone" data-id=""><i class="icon-remove-circle"></i> Clear this milestone</a></li>
|
||||
@milestones.filter(_._1.closedDate.isEmpty).map { case (milestone, _, _) =>
|
||||
<li>
|
||||
<a href="javascript:void(0);" class="milestone" data-id="@milestone.milestoneId" data-title="@milestone.title">
|
||||
@helper.html.checkicon(Some(milestone.milestoneId) == issue.milestoneId) @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>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div id="milestone-progress-area">
|
||||
@issue.milestoneId.map { milestoneId =>
|
||||
@milestones.collect { case (milestone, openCount, closeCount) if(milestone.milestoneId == milestoneId) =>
|
||||
@issues.milestones.html.progress(openCount + closeCount, closeCount)
|
||||
}
|
||||
}
|
||||
</div>
|
||||
<span id="label-milestone">
|
||||
@issue.milestoneId.map { milestoneId =>
|
||||
@milestones.collect { case (milestone, _, _) if(milestone.milestoneId == milestoneId) =>
|
||||
<span class="strong small">@milestone.title</span>
|
||||
}
|
||||
}.getOrElse(<span class="muted small">No milestone</span>)
|
||||
</span>
|
||||
<hr/>
|
||||
<div style="margin-bottom: 8px;">
|
||||
<span class="muted small strong">Assignee</span>
|
||||
@if(hasWritePermission){
|
||||
<div class="pull-right">
|
||||
@helper.html.dropdown() {
|
||||
<li><a href="javascript:void(0);" class="assign" data-name=""><i class="icon-remove-circle"></i> Clear assignee</a></li>
|
||||
@collaborators.map { collaborator =>
|
||||
<li>
|
||||
<a href="javascript:void(0);" class="assign" data-name="@collaborator">
|
||||
@helper.html.checkicon(Some(collaborator) == issue.assignedUserName)@avatar(collaborator, 20) @collaborator
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<span id="label-assigned">
|
||||
@issue.assignedUserName.map { userName =>
|
||||
@avatar(userName, 20) @user(userName, styleClass="username strong small")
|
||||
}.getOrElse(<span class="muted small">No one</span>)
|
||||
</span>
|
||||
<hr/>
|
||||
<div style="margin-bottom: 8px;">
|
||||
@defining((issue.openedUserName :: comments.map(_.commentedUserName)).distinct){ participants =>
|
||||
<div class="muted small strong">@participants.size @plural(participants.size, "participant")</div>
|
||||
@participants.map { participant => @avatarLink(participant, 20, tooltip = true) }
|
||||
}
|
||||
</div>
|
||||
<script>
|
||||
$(function(){
|
||||
$('a.toggle-label').click(function(){
|
||||
var path, icon;
|
||||
var i = $(this).children('i');
|
||||
if(i.hasClass('icon-ok')){
|
||||
path = 'delete';
|
||||
icon = 'icon-white';
|
||||
} else {
|
||||
path = 'new';
|
||||
icon = 'icon-ok';
|
||||
}
|
||||
$.post('@url(repository)/issues/@issue.issueId/label/' + path,
|
||||
{
|
||||
labelId : $(this).data('label-id')
|
||||
},
|
||||
function(data){
|
||||
i.removeClass().addClass(icon);
|
||||
$('ul.label-list').empty().html(data);
|
||||
});
|
||||
return false;
|
||||
});
|
||||
|
||||
$('a.milestone').click(function(){
|
||||
var title = $(this).data('title');
|
||||
var milestoneId = $(this).data('id');
|
||||
$.post('@url(repository)/issues/@issue.issueId/milestone',
|
||||
{
|
||||
milestoneId: milestoneId
|
||||
},
|
||||
function(data){
|
||||
console.log(data);
|
||||
$('a.milestone i.icon-ok').attr('class', 'icon-white');
|
||||
if(milestoneId == ''){
|
||||
$('#label-milestone').html($('<span class="muted small">').text('No milestone'));
|
||||
$('#milestone-progress-area').empty();
|
||||
} else {
|
||||
$('#label-milestone').html($('<span class="strong small">').text(title));
|
||||
$('#milestone-progress-area').html(data);
|
||||
$('a.milestone[data-id=' + milestoneId + '] i').attr('class', 'icon-ok');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$('a.assign').click(function(){
|
||||
var $this = $(this);
|
||||
var userName = $this.data('name');
|
||||
$.post('@url(repository)/issues/@issue.issueId/assign',
|
||||
{
|
||||
assignedUserName: userName
|
||||
},
|
||||
function(){
|
||||
$('a.assign i.icon-ok').attr('class', 'icon-white');
|
||||
if(userName == ''){
|
||||
$('#label-assigned').html($('<span class="muted small">').text('No one'));
|
||||
} else {
|
||||
$('#label-assigned').empty()
|
||||
.append($this.find('img.avatar-mini').clone(false)).append(' ')
|
||||
.append($('<a class="username strong small">').attr('href', '@context.path/' + userName).text(userName));
|
||||
$('a.assign[data-name=' + jqSelectorEscape(userName) + '] i').attr('class', 'icon-ok');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
||||
@@ -1,4 +1,7 @@
|
||||
@(issueLabels: List[model.Label])
|
||||
@if(issueLabels.isEmpty){
|
||||
<li><span class="muted small">None yet</span></li>
|
||||
}
|
||||
@issueLabels.map { label =>
|
||||
<li><span class="issue-label" style="background-color: #@label.color; color: #@label.fontColor;">@label.labelName</span></li>
|
||||
}
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
@(issue: model.Issue,
|
||||
issueLabels: List[model.Label],
|
||||
labels: List[model.Label],
|
||||
hasWritePermission: Boolean,
|
||||
repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context)
|
||||
@import view.helpers._
|
||||
<div style="margin-bottom: 8px;">
|
||||
<span class="strong">Labels</span>
|
||||
@if(hasWritePermission){
|
||||
<div class="pull-right">
|
||||
@helper.html.dropdown(right = true) {
|
||||
@labels.map { label =>
|
||||
<li>
|
||||
<a href="#" class="toggle-label" data-label-id="@label.labelId">
|
||||
@helper.html.checkicon(issueLabels.exists(_.labelId == label.labelId))
|
||||
<span class="label" style="background-color: #@label.color;"> </span>
|
||||
@label.labelName
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<ul class="label-list nav nav-pills nav-stacked">
|
||||
@labellist(issueLabels)
|
||||
</ul>
|
||||
<script>
|
||||
$(function(){
|
||||
$('a.toggle-label').click(function(){
|
||||
var path, icon;
|
||||
var i = $(this).children('i');
|
||||
if(i.hasClass('icon-ok')){
|
||||
path = 'delete';
|
||||
icon = 'icon-white';
|
||||
} else {
|
||||
path = 'new';
|
||||
icon = 'icon-ok';
|
||||
}
|
||||
$.post('@url(repository)/issues/@issue.issueId/label/' + path,
|
||||
{
|
||||
labelId : $(this).data('label-id')
|
||||
},
|
||||
function(data){
|
||||
i.removeClass().addClass(icon);
|
||||
$('ul.label-list').empty().html(data);
|
||||
});
|
||||
return false;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -1,45 +1,61 @@
|
||||
@(label: Option[model.Label], repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context)
|
||||
@import context._
|
||||
@import view.helpers._
|
||||
@defining((if(label.isEmpty) ("new", 190, 4) else ("edit", 180, 8))){ case (mode, width, margin) =>
|
||||
<div id="@(mode)LabelArea">
|
||||
<form method="POST" id="edit-label-form" validate="true" style="margin-bottom: 8px;"
|
||||
action="@url(repository)/issues/label/@{if(mode == "new") "new" else label.get.labelId + "/edit"}">
|
||||
<span id="error-@(mode)LabelName" class="error"></span>
|
||||
<input type="text" name="@(mode)LabelName" id="@(mode)LabelName" style="width: @(width)px; margin-left: @(margin)px; margin-bottom: 0px;" value="@label.map(_.labelName)"@if(mode == "new"){ placeholder="New label name"}/>
|
||||
<span id="error-@(mode)Color" class="error"></span>
|
||||
<div class="input-append color bscp" data-color="#@label.map(_.color).getOrElse("888888")" data-color-format="hex" id="@(mode)Color" style="width: @(width)px; margin-bottom: 0px;">
|
||||
<input type="text" class="span3" name="@(mode)Color" value="#@label.map(_.color)" readonly style="width: @(width - 12)px; margin-left: @(margin)px;">
|
||||
@defining(label.map(_.labelId).getOrElse("new")){ labelId =>
|
||||
<div id="edit-label-area-@labelId">
|
||||
<form style="margin-bottom: 0px;">
|
||||
<input type="text" id="labelName-@labelId" style="width: 300px; margin-bottom: 0px;" value="@label.map(_.labelName)"@if(labelId == "new"){ placeholder="New label name"}/>
|
||||
<div id="label-color-@labelId" class="input-append color bscp" data-color="#@label.map(_.color).getOrElse("888888")" data-color-format="hex" style="width: 100px; margin-bottom: 0px;">
|
||||
<input type="text" class="span3" id="labelColor-@labelId" value="#@label.map(_.color)" readonly style="width: 100px;">
|
||||
<span class="add-on"><i style="background-color: #@label.map(_.color).getOrElse("888888");"></i></span>
|
||||
</div>
|
||||
<input type="submit" class="btn" style="margin-left: @(margin)px; margin-bottom: 0px;" value="@if(mode == "new"){Create} else {Save}"/>
|
||||
@if(mode == "edit"){
|
||||
<input type="hidden" name="editLabelId" value="@label.map(_.labelId)"/>
|
||||
}
|
||||
<script>
|
||||
$('div#label-color-@labelId').colorpicker();
|
||||
</script>
|
||||
<span id="label-error-@labelId" class="error" style="padding-left: 40px;"></span>
|
||||
<span class="pull-right">
|
||||
<input type="button" id="cancel-@labelId" class="btn label-edit-cancel" value="Cancel">
|
||||
<input type="button" id="submit-@labelId" class="btn btn-success" style="margin-bottom: 0px;" value="@(if(labelId == "new") "Create label" else "Save changes")"/>
|
||||
</span>
|
||||
</form>
|
||||
</div>
|
||||
<script>
|
||||
$(function(){
|
||||
@if(mode == "new"){
|
||||
$('#newColor').colorpicker();
|
||||
$('#submit-@labelId').click(function(e){
|
||||
$.post('@url(repository)/issues/labels/@{if(labelId == "new") "new" else labelId + "/edit"}', {
|
||||
'labelName' : $('#labelName-@labelId').val(),
|
||||
'labelColor': $('#labelColor-@labelId').val()
|
||||
}, function(data, status){
|
||||
$('div#edit-label-area-@labelId').remove();
|
||||
@if(labelId == "new"){
|
||||
$('#new-label-table').hide();
|
||||
// Insert row into the top of table
|
||||
$('#label-row-header').after(data);
|
||||
} else {
|
||||
$('#editColor').colorpicker();
|
||||
|
||||
$('#edit-label-form').submit(function(e){
|
||||
$.ajax($(this).attr('action'), {
|
||||
type: 'POST',
|
||||
data: $(this).serialize()
|
||||
})
|
||||
.done(function(data){
|
||||
$('#label-edit').parent().empty().html(data);
|
||||
})
|
||||
.fail(function(data, status){
|
||||
displayErrors($.parseJSON(data.responseText));
|
||||
// Replace table row
|
||||
$('#label-row-@labelId').after(data).remove();
|
||||
}
|
||||
}).fail(function(xhr, status, error){
|
||||
var errors = JSON.parse(xhr.responseText);
|
||||
if(errors.labelName){
|
||||
$('span#label-error-@labelId').text(errors.labelName);
|
||||
} else if(errors.labelColor){
|
||||
$('span#label-error-@labelId').text(errors.labelColor);
|
||||
} else {
|
||||
$('span#label-error-@labelId').text('error');
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
$('#cancel-@labelId').click(function(e){
|
||||
$('div#edit-label-area-@labelId').remove();
|
||||
@if(labelId == "new"){
|
||||
$('#new-label-table').hide();
|
||||
} else {
|
||||
$('#label-@labelId').show();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
@(labels: List[model.Label], repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context)
|
||||
@import context._
|
||||
@import view.helpers._
|
||||
<div id="label-edit">
|
||||
<ul class="label-list nav nav-pills nav-stacked">
|
||||
@labels.map { label =>
|
||||
<li style="border: 1px solid white;">
|
||||
<a href="javascript:void(0);" class="label-edit-link" data-label-id="@label.labelId">
|
||||
<span class="count-right"><i class="icon-remove-circle"></i></span>
|
||||
<span style="background-color: #@label.color;" class="label-color"> </span>
|
||||
@label.labelName
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
<script>
|
||||
$(function(){
|
||||
$('i.icon-remove-circle').click(function(e){
|
||||
e.stopPropagation();
|
||||
if(confirm('Are you sure you want to delete this?')){
|
||||
$.get('@url(repository)/issues/label/' + $(this).parents('a').data('label-id') + '/delete',
|
||||
function(data){
|
||||
$('#label-edit').parent().empty().html(data);
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
$('a.label-edit-link').click(function(e){
|
||||
if($('input[name=editLabelId]').val() != $(this).data('label-id')){
|
||||
$('#editLabelArea').remove();
|
||||
var element = this;
|
||||
$.get('@url(repository)/issues/label/' + $(this).data('label-id') + '/edit',
|
||||
function(data){
|
||||
$(element).parent().append(data);
|
||||
$('div#label-edit li').css('border', '1px solid white');
|
||||
$(element).parent().css('border', '1px solid #eee');
|
||||
}
|
||||
);
|
||||
} else {
|
||||
$('#editLabelArea').remove();
|
||||
$('div#label-edit li').css('border', '1px solid white');
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
36
src/main/twirl/issues/labels/label.scala.html
Normal file
@@ -0,0 +1,36 @@
|
||||
@(label: model.Label,
|
||||
counts: Map[String, Int],
|
||||
repository: service.RepositoryService.RepositoryInfo,
|
||||
hasWritePermission: Boolean)(implicit context: app.Context)
|
||||
@import context._
|
||||
@import view.helpers._
|
||||
<tr id="label-row-@label.labelId">
|
||||
<td style="padding-top: 15px; padding-bottom: 15px;">
|
||||
<div class="milestone row-fluid" id="label-@label.labelId">
|
||||
<div class="span8">
|
||||
<div style="margin-top: 6px">
|
||||
<a href="@url(repository)/issues?labels=@urlEncode(label.labelName)" id="label-row-content-@label.labelId">
|
||||
<span style="background-color: #@label.color; color: #@label.fontColor; padding: 8px; font-size: 120%; border-radius: 4px;">
|
||||
<img src="@assets/common/images/label_@(if(label.fontColor == "ffffff") "white" else "black").png" style="width: 12px;"/>
|
||||
@label.labelName
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="@if(hasWritePermission){span2} else {span4}">
|
||||
<div class="pull-right">
|
||||
<span class="muted">@counts.get(label.labelName).getOrElse(0) open issues</span>
|
||||
</div>
|
||||
</div>
|
||||
@if(hasWritePermission){
|
||||
<div class="span2">
|
||||
<div class="pull-right">
|
||||
<a href="javascript:void(0);" onclick="editLabel(@label.labelId)">Edit</a>
|
||||
|
||||
<a href="javascript:void(0);" onclick="deleteLabel(@label.labelId)">Delete</a>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
66
src/main/twirl/issues/labels/list.scala.html
Normal file
@@ -0,0 +1,66 @@
|
||||
@(labels: List[model.Label],
|
||||
counts: Map[String, Int],
|
||||
repository: service.RepositoryService.RepositoryInfo,
|
||||
hasWritePermission: Boolean)(implicit context: app.Context)
|
||||
@import context._
|
||||
@import view.helpers._
|
||||
@html.main(s"Labels - ${repository.owner}/${repository.name}"){
|
||||
@html.menu("issues", repository){
|
||||
@issues.html.tab("labels", hasWritePermission, repository)
|
||||
<table class="table table-bordered table-hover table-issues" id="new-label-table" style="display: none;">
|
||||
<tr><td></td></tr>
|
||||
</table>
|
||||
<table class="table table-bordered table-hover table-issues">
|
||||
<tr id="label-row-header">
|
||||
<th style="background-color: #eee;">
|
||||
<span class="small">@labels.size labels</span>
|
||||
</th>
|
||||
</tr>
|
||||
@labels.map { label =>
|
||||
@_root_.issues.labels.html.label(label, counts, repository, hasWritePermission)
|
||||
}
|
||||
@if(labels.isEmpty){
|
||||
<tr>
|
||||
<td style="padding: 20px; background-color: #eee; text-align: center;">
|
||||
No labels to show.
|
||||
@if(hasWritePermission){
|
||||
<a href="@url(repository)/issues/labels/new">Create a new label.</a>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</table>
|
||||
}
|
||||
}
|
||||
<script>
|
||||
$(function(){
|
||||
$('#new-label-button').click(function(e){
|
||||
if($('#new-label-area').size() != 0){
|
||||
$('#new-label-table').hide();
|
||||
$('#new-label-area').remove();
|
||||
} else {
|
||||
$.get('@url(repository)/issues/labels/new',
|
||||
function(data){
|
||||
$('#new-label-table').show().find('tr td').append(data);
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function deleteLabel(labelId){
|
||||
if(confirm('Once you delete this label, there is no going back.\nAre you sure?')){
|
||||
$.post('@url(repository)/issues/labels/' + labelId + '/delete', function(){
|
||||
$('tr#label-row-' + labelId).remove();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function editLabel(labelId){
|
||||
$.get('@url(repository)/issues/labels/' + labelId + '/edit',
|
||||
function(data){
|
||||
$('#label-' + labelId).hide().parent().append(data);
|
||||
}
|
||||
);
|
||||
}
|
||||
</script>
|
||||
@@ -1,143 +1,25 @@
|
||||
@(issues: List[(model.Issue, List[model.Label], Int)],
|
||||
@(target: String,
|
||||
issues: List[service.IssuesService.IssueInfo],
|
||||
page: Int,
|
||||
collaborators: List[String],
|
||||
milestones: List[model.Milestone],
|
||||
labels: List[model.Label],
|
||||
openCount: Int,
|
||||
closedCount: Int,
|
||||
allCount: Int,
|
||||
assignedCount: Option[Int],
|
||||
createdByCount: Option[Int],
|
||||
labelCounts: Map[String, Int],
|
||||
condition: service.IssuesService.IssueSearchCondition,
|
||||
filter: String,
|
||||
repository: service.RepositoryService.RepositoryInfo,
|
||||
hasWritePermission: Boolean)(implicit context: app.Context)
|
||||
@import context._
|
||||
@import view.helpers._
|
||||
@html.main(s"Issues - ${repository.owner}/${repository.name}", Some(repository)){
|
||||
@html.menu("issues", repository){
|
||||
@tab("issues", false, repository)
|
||||
<div class="row-fluid">
|
||||
<div class="span3">
|
||||
<ul class="nav nav-pills nav-stacked">
|
||||
<li@if(filter == "all"){ class="active"}>
|
||||
<a href="@url(repository)/issues@condition.toURL">
|
||||
<span class="count-right">@allCount</span>
|
||||
Everyone's Issues
|
||||
</a>
|
||||
</li>
|
||||
@if(loginAccount.isDefined){
|
||||
<li@if(filter == "assigned"){ class="active"}>
|
||||
<a href="@url(repository)/issues/assigned/@loginAccount.map(_.userName)@condition.toURL">
|
||||
<span class="count-right">@assignedCount</span>
|
||||
Assigned to you
|
||||
</a>
|
||||
</li>
|
||||
<li@if(filter == "created_by"){ class="active"}>
|
||||
<a href="@url(repository)/issues/created_by/@loginAccount.map(_.userName)@condition.toURL">
|
||||
<span class="count-right">@createdByCount</span>
|
||||
Created by you
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
<hr/>
|
||||
@if(condition.milestoneId.isEmpty){
|
||||
<span class="muted small">No milestone selected</span>
|
||||
} else {
|
||||
@if(condition.milestoneId.get.isEmpty){
|
||||
<span class="muted small">Issues with no milestone</span>
|
||||
} else {
|
||||
<span class="muted small">Milestone:</span> @milestones.find(_.milestoneId == condition.milestoneId.get.get).map(_.title)
|
||||
}
|
||||
}
|
||||
@helper.html.dropdown() {
|
||||
@if(condition.milestoneId.isDefined){
|
||||
<li>
|
||||
<a href="@condition.copy(milestoneId = None).toURL">
|
||||
<i class="icon-remove-circle"></i> Clear milestone filter
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
<li>
|
||||
<a href="@condition.copy(milestoneId = Some(None)).toURL">
|
||||
@helper.html.checkicon(condition.milestoneId == Some(None)) Issues with no milestone
|
||||
</a>
|
||||
</li>
|
||||
@milestones.filter(_.closedDate.isEmpty).map { milestone =>
|
||||
<li>
|
||||
<a href="@condition.copy(milestoneId = Some(Some(milestone.milestoneId))).toURL">
|
||||
@helper.html.checkicon(condition.milestoneId == Some(Some(milestone.milestoneId))) @milestone.title
|
||||
<div class="small" style="padding-left: 20px;">
|
||||
@milestone.dueDate.map { dueDate =>
|
||||
@if(isPast(dueDate)){
|
||||
<img src="@assets/common/images/alert_mono.png"/>Due in @date(dueDate)
|
||||
} else {
|
||||
<span class="muted">Due in @date(dueDate)</span>
|
||||
}
|
||||
}.getOrElse {
|
||||
<span class="muted">No due date</span>
|
||||
}
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
}
|
||||
@if(condition.milestoneId.isDefined && condition.milestoneId.get.isDefined){
|
||||
@milestones.find(_.milestoneId == condition.milestoneId.get.get).map { milestone =>
|
||||
<div style="margin-top: 4px;">
|
||||
@_root_.issues.milestones.html.progress(openCount + closedCount, closedCount, false)
|
||||
</div>
|
||||
<span class="muted small">@openCount open issues</span>
|
||||
@if(milestone.closedDate.isDefined){
|
||||
@milestone.closedDate.map { closedDate =>
|
||||
<span class="small">Closed in @date(closedDate)</span>
|
||||
}
|
||||
} else {
|
||||
@milestone.dueDate.map { dueDate =>
|
||||
@if(isPast(dueDate)){
|
||||
<img src="@assets/common/images/alert.png"/><span class="small milestone-alert">Due in @date(dueDate)</span>
|
||||
} else {
|
||||
<span class="small">Due in @date(dueDate)</span>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
<hr/>
|
||||
<span class="strong">Labels</span>
|
||||
<div>
|
||||
<div id="label-list">
|
||||
<ul class="label-list nav nav-pills nav-stacked">
|
||||
@labels.map { label =>
|
||||
<li>
|
||||
<a href="@condition.copy(labels = (if(condition.labels.contains(label.labelName)) condition.labels - label.labelName else condition.labels + label.labelName)).toURL"
|
||||
@if(condition.labels.contains(label.labelName)){style="background-color: #@label.color; color: #@label.fontColor;"}>
|
||||
<span class="count-right">@labelCounts.getOrElse(label.labelName, 0)</span>
|
||||
<span style="background-color: #@label.color;" class="label-color"> </span>
|
||||
@label.labelName
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@if(hasWritePermission){
|
||||
<hr/>
|
||||
<input type="button" class="btn btn-block" id="manageLabel" data-toggle="button" value="Manage Labels"/>
|
||||
<br/>
|
||||
<span class="strong">New label</span>
|
||||
@_root_.issues.labels.html.edit(None, repository)
|
||||
}
|
||||
</div>
|
||||
@***** show issue list *****@
|
||||
@html.main((if(target == "issues") "Issues" else "Pull requests") + s" - ${repository.owner}/${repository.name}", Some(repository)){
|
||||
@html.menu(target, repository){
|
||||
@tab(target, true, repository)
|
||||
@listparts(issues, page, openCount, closedCount, condition, collaborators, milestones, labels, Some(repository), hasWritePermission)
|
||||
</div>
|
||||
@if(hasWritePermission){
|
||||
<form id="batcheditForm" method="POST">
|
||||
<input type="hidden" name="value"/>
|
||||
<input type="hidden" name="checked"/>
|
||||
<input type="hidden" name="from" value="@target"/>
|
||||
</form>
|
||||
}
|
||||
}
|
||||
@@ -145,20 +27,34 @@
|
||||
@if(hasWritePermission){
|
||||
<script>
|
||||
$(function(){
|
||||
$('#manageLabel').click(function(){
|
||||
if($(this).data('toggle-state')){
|
||||
location.href = '@url(repository)/issues';
|
||||
} else {
|
||||
$(this).data('toggle-state', 'on');
|
||||
$.get('@url(repository)/issues/label/edit', function(data){
|
||||
$('#label-list').parent().empty().html(data);
|
||||
});
|
||||
$('a.header-link').mouseover(function(e){
|
||||
var target = e.target;
|
||||
if(e.target.tagName != 'A'){
|
||||
target = e.target.parentElement;
|
||||
}
|
||||
$(target).children('strong' ).css('color', '#0088cc');
|
||||
$(target).children('img.header-icon-hover').css('display', 'inline');
|
||||
$(target).children('img.header-icon' ).css('display', 'none');
|
||||
});
|
||||
|
||||
$('a.header-link').mouseout(function(e){
|
||||
var target = e.target;
|
||||
if(e.target.tagName != 'A'){
|
||||
target = e.target.parentElement;
|
||||
}
|
||||
$(target).children('strong' ).css('color', 'black');
|
||||
$(target).children('img.header-icon-hover').css('display', 'none');
|
||||
$(target).children('img.header-icon' ).css('display', 'inline');
|
||||
});
|
||||
|
||||
$('.table-issues input[type=checkbox]').change(function(){
|
||||
$('.table-issues button').prop('disabled',
|
||||
!$('.table-issues input[type=checkbox]').filter(':checked').length);
|
||||
if($('.table-issues input[type=checkbox]').filter(':checked').length == 0){
|
||||
$('#table-issues-control').show();
|
||||
$('#table-issues-batchedit').hide();
|
||||
} else {
|
||||
$('#table-issues-control').hide();
|
||||
$('#table-issues-batchedit').show();
|
||||
}
|
||||
}).filter(':first').change();
|
||||
|
||||
var submitBatchEdit = function(action, value) {
|
||||
@@ -170,8 +66,8 @@ $(function(){
|
||||
form.submit();
|
||||
};
|
||||
|
||||
$('#state').click(function(){
|
||||
submitBatchEdit('@url(repository)/issues/batchedit/state', $(this).text().toLowerCase());
|
||||
$('a.toggle-state').click(function(){
|
||||
submitBatchEdit('@url(repository)/issues/batchedit/state', $(this).data('id'));
|
||||
});
|
||||
$('a.toggle-label').click(function(){
|
||||
submitBatchEdit('@url(repository)/issues/batchedit/label', $(this).data('id'));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@(issues: List[(model.Issue, List[model.Label], Int)],
|
||||
@(issues: List[service.IssuesService.IssueInfo],
|
||||
page: Int,
|
||||
openCount: Int,
|
||||
closedCount: Int,
|
||||
@@ -10,37 +10,77 @@
|
||||
hasWritePermission: Boolean = false)(implicit context: app.Context)
|
||||
@import context._
|
||||
@import view.helpers._
|
||||
|
||||
<div class="span9">
|
||||
@if(condition.labels.nonEmpty || condition.milestoneId.isDefined){
|
||||
<a href="@condition.copy(labels = Set.empty, milestoneId = None).toURL" id="clear-filter">
|
||||
<i class="icon-remove-circle"></i> Clear milestone and label filters
|
||||
@import service.IssuesService.IssueInfo
|
||||
@if(condition.nonEmpty){
|
||||
<div>
|
||||
<a href="@service.IssuesService.IssueSearchCondition().toURL" class="header-link">
|
||||
<img src="@assets/common/images/clear.png" class="header-icon"/>
|
||||
<img src="@assets/common/images/clear_hover.png" class="header-icon-hover" style="display: none;"/>
|
||||
<span class="strong">Clear current search query, filters, and sorts</span>
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
@if(condition.repo.isDefined){
|
||||
<a href="@condition.copy(repo = None).toURL" id="clear-filter">
|
||||
<i class="icon-remove-circle"></i> Clear filter on @condition.repo
|
||||
<table class="table table-bordered table-hover table-issues">
|
||||
<tr>
|
||||
<th style="background-color: #eee;">
|
||||
<input type="checkbox"/>
|
||||
<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("Author", flat = true) {
|
||||
@collaborators.map { collaborator =>
|
||||
<li>
|
||||
<a href="@condition.copy(author = (if(condition.author == Some(collaborator)) None else Some(collaborator))).toURL">
|
||||
@helper.html.checkicon(condition.author == Some(collaborator))
|
||||
@avatar(collaborator, 20) @collaborator
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
<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
|
||||
){
|
||||
}
|
||||
@helper.html.dropdown("Label", flat = true) {
|
||||
@labels.map { label =>
|
||||
<li>
|
||||
<a href="@condition.copy(labels = (if(condition.labels.contains(label.labelName)) condition.labels - label.labelName else condition.labels + label.labelName)).toURL">
|
||||
@helper.html.checkicon(condition.labels.contains(label.labelName))
|
||||
<span style="background-color: #@label.color;" class="label-color"> </span>
|
||||
@label.labelName
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
}
|
||||
@helper.html.dropdown("Milestone", flat = true) {
|
||||
<li>
|
||||
<a href="@condition.copy(milestoneId = Some(None)).toURL">
|
||||
@helper.html.checkicon(condition.milestoneId == Some(None)) Issues with no milestone
|
||||
</a>
|
||||
</li>
|
||||
@milestones.filter(_.closedDate.isEmpty).map { milestone =>
|
||||
<li>
|
||||
<a href="@condition.copy(milestoneId = Some(Some(milestone.milestoneId))).toURL">
|
||||
@helper.html.checkicon(condition.milestoneId == Some(Some(milestone.milestoneId))) @milestone.title
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
}
|
||||
@helper.html.dropdown("Assignee", flat = true) {
|
||||
@collaborators.map { collaborator =>
|
||||
<li>
|
||||
<a href="@condition.copy(assigned = Some(collaborator)).toURL">
|
||||
@helper.html.checkicon(condition.assigned == Some(collaborator))
|
||||
@avatar(collaborator, 20) @collaborator
|
||||
</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
|
||||
@@ -72,7 +112,40 @@
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
<table class="table table-bordered table-hover table-issues">
|
||||
</div>
|
||||
@if(hasWritePermission){
|
||||
<div class="pull-right" id="table-issues-batchedit">
|
||||
@helper.html.dropdown("Mark as", flat = true) {
|
||||
<li><a href="javascript:void(0);" class="toggle-state" data-id="open">Open</a></li>
|
||||
<li><a href="javascript:void(0);" class="toggle-state" data-id="close">Close</a></li>
|
||||
}
|
||||
@helper.html.dropdown("Label", flat = true) {
|
||||
@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("Milestone", flat = true) {
|
||||
<li><a href="javascript:void(0);" class="toggle-milestone" data-id="">No milestone</a></li>
|
||||
@milestones.filter(_.closedDate.isEmpty).map { milestone =>
|
||||
<li><a href="javascript:void(0);" class="toggle-milestone" data-id="@milestone.milestoneId">@milestone.title</a></li>
|
||||
}
|
||||
}
|
||||
@helper.html.dropdown("Assignee", flat = true) {
|
||||
<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>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</th>
|
||||
</tr>
|
||||
@if(issues.isEmpty){
|
||||
<tr>
|
||||
<td style="padding: 20px; background-color: #eee; text-align: center;">
|
||||
@@ -86,93 +159,41 @@
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
} else {
|
||||
@if(hasWritePermission){
|
||||
}
|
||||
@issues.map { case IssueInfo(issue, labels, milestone, commentCount) =>
|
||||
<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_mono.png"/>Due in @date(dueDate)
|
||||
} else {
|
||||
<span class="muted">Due in @date(dueDate)</span>
|
||||
}
|
||||
}.getOrElse {
|
||||
<span class="muted">No due date</span>
|
||||
}
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
}
|
||||
@issues.map { case (issue, labels, commentCount) =>
|
||||
<tr>
|
||||
<td>
|
||||
<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){
|
||||
<img src="@assets/common/images/pullreq-@(if(issue.closed) "closed" else "open").png"/>
|
||||
} 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" style="margin-right: 20px;"/>
|
||||
@if(repository.isEmpty){
|
||||
<a href="@path/@issue.userName/@issue.repositoryName">@issue.repositoryName</a> ・
|
||||
}
|
||||
@if(issue.isPullRequest){
|
||||
<a href="@path/@issue.userName/@issue.repositoryName/pull/@issue.issueId" class="issue-title">@issue.title</a>
|
||||
} else {
|
||||
<a href="@path/@issue.userName/@issue.repositoryName/issues/@issue.issueId" class="issue-title">@issue.title</a>
|
||||
}
|
||||
@labels.map { label =>
|
||||
<span class="label-color small" style="background-color: #@label.color; color: #@label.fontColor; padding-left: 4px; padding-right: 4px">@label.labelName</span>
|
||||
}
|
||||
<span class="pull-right muted">
|
||||
<span class="pull-right small">
|
||||
@issue.assignedUserName.map { userName =>
|
||||
@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){
|
||||
<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: 40px; 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>
|
||||
@if(hasWritePermission){
|
||||
</label>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@@ -180,5 +201,4 @@
|
||||
<div class="pull-right">
|
||||
@helper.html.paginator(page, (if(condition.state == "open") openCount else closedCount), service.IssuesService.IssueLimit, 10, condition.toURL)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -2,12 +2,18 @@
|
||||
@import context._
|
||||
@import view.helpers._
|
||||
@html.main(s"Milestones - ${repository.owner}/${repository.name}"){
|
||||
@html.menu("milestones", repository){
|
||||
@html.menu("issues", repository){
|
||||
@if(milestone.isEmpty){
|
||||
<h4>New milestone</h4>
|
||||
<div class="muted">Create a new milestone to help organize your issues and pull requests.</div>
|
||||
} else {
|
||||
@issues.html.tab("milestones", false, repository)
|
||||
<br><br>
|
||||
}
|
||||
<hr style="margin-top: 12px; margin-bottom: 18px;" class="fill-width"/>
|
||||
<form method="POST" action="@url(repository)/issues/milestones/@if(milestone.isEmpty){new}else{@milestone.get.milestoneId/edit}" validate="true">
|
||||
<fieldset>
|
||||
<label for="title"><string>Title</string></label>
|
||||
<input type="text" id="title" name="title" style="width: 400px;" value="@milestone.map(_.title)"/>
|
||||
<input type="text" id="title" name="title" style="width: 500px;" value="@milestone.map(_.title)" placeholder="Title"/>
|
||||
<span id="error-title" class="error"></span>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
|
||||
@@ -6,70 +6,71 @@
|
||||
@import view.helpers._
|
||||
@html.main(s"Milestones - ${repository.owner}/${repository.name}"){
|
||||
@html.menu("issues", repository){
|
||||
@issues.html.tab("milestones", false, repository)
|
||||
<div class="row-fluid">
|
||||
<div class="span3">
|
||||
<ul class="nav nav-pills nav-stacked">
|
||||
<li@if(state == "open"){ class="active"}>
|
||||
<a href="?state=open">
|
||||
<span class="count-right">@milestones.filter(_._1.closedDate.isEmpty).size</span>
|
||||
Open Milestones
|
||||
@issues.html.tab("milestones", hasWritePermission, repository)
|
||||
<table class="table table-bordered table-hover table-issues">
|
||||
<tr>
|
||||
<th style="background-color: #eee;">
|
||||
<span class="small">
|
||||
<a class="button-link@if(state == "open"){ selected}" href="?state=open">
|
||||
<img src="@assets/common/images/milestone@(if(state == "open"){"-active"}).png"/>
|
||||
@milestones.filter(_._1.closedDate.isEmpty).size Open
|
||||
</a>
|
||||
<a class="button-link@if(state == "closed"){ selected}" href="?state=closed">
|
||||
<img src="@assets/common/images/milestone@(if(state == "closed"){"-active"}).png"/>
|
||||
@milestones.filter(_._1.closedDate.isDefined).size Closed
|
||||
</a>
|
||||
</li>
|
||||
<li@if(state == "closed"){ class="active"}>
|
||||
<a href="?state=closed">
|
||||
<span class="count-right">@milestones.filter(_._1.closedDate.isDefined).size</span>
|
||||
Closed Milestones
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
@if(hasWritePermission){
|
||||
<hr>
|
||||
<a href="@url(repository)/issues/milestones/new" class="btn btn-block">Create a new milestone</a>
|
||||
}
|
||||
</div>
|
||||
<div class="span9">
|
||||
<table class="table table-bordered table-hover">
|
||||
</span>
|
||||
</th>
|
||||
</tr>
|
||||
@defining(milestones.filter { case (milestone, _, _) =>
|
||||
milestone.closedDate.map(_ => state == "closed").getOrElse(state == "open")
|
||||
}){ milestones =>
|
||||
@milestones.map { case (milestone, openCount, closedCount) =>
|
||||
<tr>
|
||||
<td>
|
||||
<td style="padding-top: 15px; padding-bottom: 15px;">
|
||||
<div class="milestone row-fluid">
|
||||
<div class="span4">
|
||||
<a href="@url(repository)/issues?milestone=@milestone.milestoneId&state=open" class="milestone-title">@milestone.title</a><br>
|
||||
<a href="@url(repository)/issues?milestone=@milestone.milestoneId&state=open" class="milestone-title">@milestone.title</a>
|
||||
<div style="margin-top: 6px">
|
||||
@if(milestone.closedDate.isDefined){
|
||||
<span class="muted">Closed @datetime(milestone.closedDate.get)</span>
|
||||
} else {
|
||||
@milestone.dueDate.map { dueDate =>
|
||||
@if(isPast(dueDate)){
|
||||
<img src="@assets/common/images/alert.png"/><span class="muted milestone-alert">Due in @date(dueDate)</span>
|
||||
<img src="@assets/common/images/alert.png"/><span class="muted milestone-alert">Due by @date(dueDate)</span>
|
||||
} else {
|
||||
<span class="muted">Due in @date(dueDate)</span>
|
||||
<span class="muted">Due by @date(dueDate)</span>
|
||||
}
|
||||
}.getOrElse {
|
||||
<span class="muted">No due date</span>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="span8">
|
||||
<div class="milestone-menu">
|
||||
<div class="pull-right">
|
||||
@if(hasWritePermission){
|
||||
<a href="@url(repository)/issues/milestones/@milestone.milestoneId/edit">Edit
|
||||
@if(milestone.closedDate.isDefined){
|
||||
<a href="@url(repository)/issues/milestones/@milestone.milestoneId/open">Open</a>
|
||||
@progress(openCount + closedCount, closedCount)
|
||||
<div>
|
||||
<div>
|
||||
@if(closedCount == 0){
|
||||
0%
|
||||
} else {
|
||||
<a href="@url(repository)/issues/milestones/@milestone.milestoneId/close">Close</a>
|
||||
@((closedCount.toDouble / (openCount + closedCount).toDouble * 100).toInt)%
|
||||
} <span class="muted">complete</span>
|
||||
@openCount <span class="muted">open</span>
|
||||
@closedCount <span class="muted">closed</span>
|
||||
</div>
|
||||
<div class="milestone-menu">
|
||||
@if(hasWritePermission){
|
||||
<a href="@url(repository)/issues/milestones/@milestone.milestoneId/edit">Edit</a>
|
||||
@if(milestone.closedDate.isDefined){
|
||||
<a href="@url(repository)/issues/milestones/@milestone.milestoneId/open">Open</a>
|
||||
} else {
|
||||
<a href="@url(repository)/issues/milestones/@milestone.milestoneId/close">Close</a>
|
||||
}
|
||||
<a href="@url(repository)/issues/milestones/@milestone.milestoneId/delete" class="delete">Delete</a>
|
||||
}
|
||||
<a href="@url(repository)/issues?milestone=@milestone.milestoneId&state=open">Browse issues</a>
|
||||
</div>
|
||||
<span class="muted">@closedCount closed - @openCount open</span>
|
||||
</div>
|
||||
@progress(openCount + closedCount, closedCount, true)
|
||||
</div>
|
||||
</div>
|
||||
@if(milestone.description.isDefined){
|
||||
@@ -92,8 +93,6 @@
|
||||
}
|
||||
}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
<script>
|
||||
|
||||
@@ -1,15 +1,6 @@
|
||||
@(total: Int, progress: Int, showPercentage: Boolean)
|
||||
@(total: Int, progress: Int)
|
||||
<div class="milestone-progress">
|
||||
@if(progress > 0){
|
||||
<span class="milestone-progress" style="width: @((progress.toDouble / total.toDouble * 100).toInt)%;"></span>
|
||||
}
|
||||
@if(showPercentage){
|
||||
<span class="milestone-percentage">
|
||||
@if(progress == 0){
|
||||
0%
|
||||
} else {
|
||||
@((progress.toDouble / total.toDouble * 100).toInt)%
|
||||
}
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -1,16 +1,28 @@
|
||||
@(active: String, create: Boolean, repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context)
|
||||
@(active: String, newButton: Boolean,
|
||||
repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context)
|
||||
@import context._
|
||||
@import view.helpers._
|
||||
<ul class="nav nav-tabs pull-left fill-width">
|
||||
<li@if(active == "issues"){ class="active"}><a href="@url(repository)/issues">Browse Issues</a></li>
|
||||
<li@if(active == "milestones"){ class="active"}><a href="@url(repository)/issues/milestones">Milestones</a></li>
|
||||
<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(create){
|
||||
<a class="btn btn-small btn-success" href="#" disabled="disabled">New Issue</a>
|
||||
} else {
|
||||
<a class="btn btn-small btn-success" href="@url(repository)/issues/new">New Issue</a>
|
||||
@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>
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
@import view.helpers._
|
||||
<div class="row-fluid">
|
||||
<div class="span10">
|
||||
@issues.html.issuedetail(issue, comments, collaborators, milestones, hasWritePermission, repository)
|
||||
@issues.html.commentlist(issue, comments, hasWritePermission, repository, Some(pullreq))
|
||||
@defining(comments.exists(_.action == "merge")){ merged =>
|
||||
@if(hasWritePermission && !issue.closed){
|
||||
@@ -67,7 +66,7 @@
|
||||
</div>
|
||||
}
|
||||
<hr/>
|
||||
@issues.html.labels(issue, issueLabels, labels, hasWritePermission, repository)
|
||||
@issues.html.issueinfo(issue, comments, issueLabels, collaborators, milestones, labels, hasWritePermission, repository)
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
@(issues: List[(model.Issue, List[model.Label], Int)],
|
||||
counts: List[service.PullRequestService.PullRequestCount],
|
||||
filter: Option[String],
|
||||
page: Int,
|
||||
openCount: Int,
|
||||
closedCount: Int,
|
||||
allCount: Int,
|
||||
condition: service.IssuesService.IssueSearchCondition,
|
||||
repository: service.RepositoryService.RepositoryInfo,
|
||||
hasWritePermission: Boolean)(implicit context: app.Context)
|
||||
@import context._
|
||||
@import view.helpers._
|
||||
@html.main(s"Pull Requests - ${repository.owner}/${repository.name}", Some(repository)){
|
||||
@html.menu("pulls", repository){
|
||||
<div class="row-fluid">
|
||||
<div class="span3">
|
||||
<ul class="nav nav-pills nav-stacked">
|
||||
<li@if(filter.isEmpty){ class="active"}>
|
||||
<a href="@url(repository)/pulls">
|
||||
<span class="count-right">@allCount</span>
|
||||
All Requests
|
||||
</a>
|
||||
</li>
|
||||
@if(loginAccount.isDefined){
|
||||
<li@if(filter.map(_ == loginAccount.get.userName).getOrElse(false)){ class="active"}>
|
||||
<a href="@url(repository)/pulls/@loginAccount.map(_.userName)">
|
||||
<span class="count-right">@counts.find(_.userName == loginAccount.get.userName).map(_.count).getOrElse(0)</span>
|
||||
Yours
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
<hr>
|
||||
<ul class="nav nav-pills nav-stacked small">
|
||||
@counts.map { user =>
|
||||
@if(loginAccount.isEmpty || loginAccount.get.userName != user.userName){
|
||||
<li@if(filter.map(_ == user.userName).getOrElse(false)){ class="active"}>
|
||||
<a href="@url(repository)/pulls/@user.userName">
|
||||
<span class="count-right">@user.count</span>
|
||||
@user.userName
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
@listparts(issues, page, openCount, closedCount, condition, Some(repository), hasWritePermission)
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -617,11 +617,81 @@ span.simplified-path {
|
||||
color: #888;
|
||||
}
|
||||
|
||||
/****************************************************************************/
|
||||
/* nav pulls group */
|
||||
/****************************************************************************/
|
||||
.nav-pills-group:after {
|
||||
display: table;
|
||||
line-height: 0;
|
||||
content: "";
|
||||
}
|
||||
|
||||
.nav-pills-group:after {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.nav-pills-group > li {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.nav-pills-group > li > a {
|
||||
padding-right: 12px;
|
||||
padding-left: 12px;
|
||||
line-height: 14px;
|
||||
color: #666;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.nav-pills-group > li > a {
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
border-left : 1px solid #e5e5e5;
|
||||
border-top : 1px solid #e5e5e5;
|
||||
border-bottom : 1px solid #e5e5e5;
|
||||
}
|
||||
|
||||
.nav-pills-group > .first > a {
|
||||
-webkit-border-radius: 4px 0 0 4px;
|
||||
-moz-border-radius: 4px 0 0 4px;
|
||||
border-radius: 4px 0 0 4px;
|
||||
}
|
||||
|
||||
.nav-pills-group > .last > a {
|
||||
-webkit-border-radius: 0 4px 4px 0;
|
||||
-moz-border-radius: 0 4px 4px 0;
|
||||
border-radius: 0 4px 4px 0;
|
||||
border-right : 1px solid #e5e5e5;
|
||||
}
|
||||
|
||||
.nav-pills-group > .active > a,
|
||||
.nav-pills-group > .active > a:hover,
|
||||
.nav-pills-group > .active > a:focus {
|
||||
color: #ffffff;
|
||||
background-color: #0088cc;
|
||||
border-color: #0088cc;
|
||||
}
|
||||
|
||||
/****************************************************************************/
|
||||
/* Issues */
|
||||
/****************************************************************************/
|
||||
.btn-group.open .dropdown-toggle.flat {
|
||||
background-image: none;
|
||||
-webkit-box-shadow: none;
|
||||
-moz-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
a.button-link {
|
||||
font-weight: normal;
|
||||
color: gray;
|
||||
}
|
||||
|
||||
a.selected {
|
||||
font-weight: bold;
|
||||
color: black;
|
||||
}
|
||||
|
||||
span.issue-status {
|
||||
display: block;
|
||||
font-size: large;
|
||||
text-align: center;
|
||||
padding: 8px;
|
||||
@@ -634,7 +704,7 @@ table.table-issues {
|
||||
a.issue-title {
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
size: 110%;
|
||||
font-size: 120%;
|
||||
}
|
||||
|
||||
ul.label-list {
|
||||
@@ -671,8 +741,7 @@ span.milestone-alert {
|
||||
}
|
||||
|
||||
a.milestone-title {
|
||||
font-size: 120%;
|
||||
font-weight: bold;
|
||||
font-size: 180%;
|
||||
}
|
||||
|
||||
div.milestone-description {
|
||||
@@ -680,13 +749,12 @@ div.milestone-description {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
div.milestone-menu {
|
||||
font-size: 80%;
|
||||
a.milestone-title {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
div.milestone-menu a {
|
||||
margin-left: 8px;
|
||||
font-weight: bold;
|
||||
div.milestone-menu {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
div.milestone-menu a.delete {
|
||||
@@ -698,13 +766,13 @@ div#milestone-progress-area {
|
||||
}
|
||||
|
||||
div#milestone-progress-area div.milestone-progress {
|
||||
width: 150px;
|
||||
width: 130px;
|
||||
margin-bottom: -6px;
|
||||
}
|
||||
|
||||
div.milestone-progress {
|
||||
position: relative;
|
||||
height: 20px;
|
||||
height: 10px;
|
||||
color: white;
|
||||
margin-bottom: 4px;
|
||||
font-weight: bold;
|
||||
@@ -725,11 +793,6 @@ span.milestone-progress {
|
||||
-moz-border-radius: 4px;
|
||||
}
|
||||
|
||||
span.milestone-percentage {
|
||||
position: absolute;
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
div.issue-header {
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
|
||||
|
Before Width: | Height: | Size: 166 B |
BIN
src/main/webapp/assets/common/images/clear.png
Normal file
|
After Width: | Height: | Size: 295 B |
BIN
src/main/webapp/assets/common/images/clear_hover.png
Normal file
|
After Width: | Height: | Size: 349 B |
BIN
src/main/webapp/assets/common/images/comment-active.png
Normal file
|
After Width: | Height: | Size: 95 B |
BIN
src/main/webapp/assets/common/images/comment.png
Normal file
|
After Width: | Height: | Size: 99 B |
BIN
src/main/webapp/assets/common/images/label_black.png
Normal file
|
After Width: | Height: | Size: 137 B |
BIN
src/main/webapp/assets/common/images/label_white.png
Normal file
|
After Width: | Height: | Size: 147 B |
BIN
src/main/webapp/assets/common/images/milestone-active.png
Normal file
|
After Width: | Height: | Size: 104 B |
BIN
src/main/webapp/assets/common/images/milestone.png
Normal file
|
After Width: | Height: | Size: 105 B |
BIN
src/main/webapp/assets/common/images/status-closed-active.png
Normal file
|
After Width: | Height: | Size: 164 B |
BIN
src/main/webapp/assets/common/images/status-closed.png
Normal file
|
After Width: | Height: | Size: 146 B |
BIN
src/main/webapp/assets/common/images/status-open-active.png
Normal file
|
After Width: | Height: | Size: 226 B |
BIN
src/main/webapp/assets/common/images/status-open.png
Normal file
|
After Width: | Height: | Size: 178 B |