Add milestone page (#2586)

This commit is contained in:
onukura
2020-12-07 04:17:30 +09:00
committed by GitHub
parent 401728d47f
commit 1b32e13113
11 changed files with 396 additions and 31 deletions

View File

@@ -34,7 +34,7 @@ object ApiMilestone {
): ApiMilestone =
ApiMilestone(
url = ApiPath(s"/api/v3/repos/${RepositoryName(repository).fullName}/milestones/${milestone.milestoneId}"),
html_url = ApiPath(s"/${RepositoryName(repository).fullName}/issues?milestone=${milestone.title}&state=open"),
html_url = ApiPath(s"/${RepositoryName(repository).fullName}/milestone/${milestone.milestoneId}"),
// label_url = ApiPath(s"/api/v3/repos/${RepositoryName(repository).fullName}/milestones/${milestone_number}/labels"),
id = milestone.milestoneId,
number = milestone.milestoneId, // use milestoneId as number

View File

@@ -88,10 +88,10 @@ trait DashboardControllerBase extends ControllerBase {
val page = IssueSearchCondition.page(request)
html.issues(
searchIssue(condition, false, (page - 1) * IssueLimit, IssueLimit, userRepos: _*),
searchIssue(condition, IssueSearchOption.Issues, (page - 1) * IssueLimit, IssueLimit, userRepos: _*),
page,
countIssue(condition.copy(state = "open"), false, userRepos: _*),
countIssue(condition.copy(state = "closed"), false, userRepos: _*),
countIssue(condition.copy(state = "open"), IssueSearchOption.Issues, userRepos: _*),
countIssue(condition.copy(state = "closed"), IssueSearchOption.Issues, userRepos: _*),
filter match {
case "assigned" => condition.copy(assigned = Some(Some(userName)))
case "mentioned" => condition.copy(mentioned = Some(userName))
@@ -118,10 +118,16 @@ trait DashboardControllerBase extends ControllerBase {
val page = IssueSearchCondition.page(request)
html.pulls(
searchIssue(condition, true, (page - 1) * PullRequestLimit, PullRequestLimit, allRepos: _*),
searchIssue(
condition,
IssueSearchOption.PullRequests,
(page - 1) * PullRequestLimit,
PullRequestLimit,
allRepos: _*
),
page,
countIssue(condition.copy(state = "open"), true, allRepos: _*),
countIssue(condition.copy(state = "closed"), true, allRepos: _*),
countIssue(condition.copy(state = "open"), IssueSearchOption.PullRequests, allRepos: _*),
countIssue(condition.copy(state = "closed"), IssueSearchOption.PullRequests, allRepos: _*),
filter match {
case "assigned" => condition.copy(assigned = Some(Some(userName)))
case "mentioned" => condition.copy(mentioned = Some(userName))

View File

@@ -369,6 +369,9 @@ trait IssuesControllerBase extends ControllerBase {
}
case _ => BadRequest()
}
if (params("uri").nonEmpty) {
redirect(params("uri"))
}
}
})
@@ -377,6 +380,9 @@ trait IssuesControllerBase extends ControllerBase {
executeBatch(repository) { issueId =>
getIssueLabel(repository.owner, repository.name, issueId, labelId) getOrElse {
registerIssueLabel(repository.owner, repository.name, issueId, labelId, true)
if (params("uri").nonEmpty) {
redirect(params("uri"))
}
}
}
} getOrElse NotFound()
@@ -387,6 +393,9 @@ trait IssuesControllerBase extends ControllerBase {
executeBatch(repository) {
updateAssignedUserName(repository.owner, repository.name, _, value, true)
}
if (params("uri").nonEmpty) {
redirect(params("uri"))
}
}
})
@@ -449,6 +458,7 @@ trait IssuesControllerBase extends ControllerBase {
params("from") match {
case "issues" => redirect(s"/${repository.owner}/${repository.name}/issues")
case "pulls" => redirect(s"/${repository.owner}/${repository.name}/pulls")
case _ =>
}
}
@@ -462,14 +472,14 @@ trait IssuesControllerBase extends ControllerBase {
html.list(
"issues",
searchIssue(condition, false, (page - 1) * IssueLimit, IssueLimit, owner -> repoName),
searchIssue(condition, IssueSearchOption.Issues, (page - 1) * IssueLimit, IssueLimit, owner -> repoName),
page,
getAssignableUserNames(owner, repoName),
getMilestones(owner, repoName),
getPriorities(owner, repoName),
getLabels(owner, repoName),
countIssue(condition.copy(state = "open"), false, owner -> repoName),
countIssue(condition.copy(state = "closed"), false, owner -> repoName),
countIssue(condition.copy(state = "open"), IssueSearchOption.Issues, owner -> repoName),
countIssue(condition.copy(state = "closed"), IssueSearchOption.Issues, owner -> repoName),
condition,
repository,
isIssueEditable(repository),

View File

@@ -1,10 +1,12 @@
package gitbucket.core.controller
import gitbucket.core.issues.milestones.html
import gitbucket.core.service.{AccountService, MilestonesService, RepositoryService}
import gitbucket.core.service.IssuesService.{IssueLimit, IssueSearchCondition}
import gitbucket.core.service.{AccountService, IssueSearchOption, MilestonesService, RepositoryService}
import gitbucket.core.util.Implicits._
import gitbucket.core.util.{ReferrerAuthenticator, WritableUsersAuthenticator}
import gitbucket.core.util.SyntaxSugars._
import gitbucket.core.view.helpers.{getAssignableUserNames, getLabels, getPriorities, searchIssue}
import org.scalatra.forms._
import org.scalatra.i18n.Messages
@@ -36,6 +38,34 @@ trait MilestonesControllerBase extends ControllerBase {
)
})
get("/:owner/:repository/milestone/:id")(referrersOnly { repository =>
val milestone = getMilestone(repository.owner, repository.name, params("id").toInt)
val page = IssueSearchCondition.page(request)
val condition = IssueSearchCondition(
request,
milestone.get.title
)
html.milestone(
condition.state,
searchIssue(
condition,
IssueSearchOption.Both,
(page - 1) * IssueLimit,
IssueLimit,
repository.owner -> repository.name
),
page,
getAssignableUserNames(repository.owner, repository.name),
getPriorities(repository.owner, repository.name),
getLabels(repository.owner, repository.name),
condition,
getMilestonesWithIssueCount(repository.owner, repository.name)
.filter(p => p._1.milestoneId == milestone.get.milestoneId),
repository,
hasDeveloperRole(repository.owner, repository.name, context.loginAccount)
)
})
get("/:owner/:repository/issues/milestones/new")(writableUsersOnly {
html.edit(None, _)
})

View File

@@ -643,14 +643,20 @@ trait PullRequestsControllerBase extends ControllerBase {
gitbucket.core.issues.html.list(
"pulls",
searchIssue(condition, true, (page - 1) * PullRequestLimit, PullRequestLimit, owner -> repoName),
searchIssue(
condition,
IssueSearchOption.PullRequests,
(page - 1) * PullRequestLimit,
PullRequestLimit,
owner -> repoName
),
page,
getAssignableUserNames(owner, repoName),
getMilestones(owner, repoName),
getPriorities(owner, repoName),
getLabels(owner, repoName),
countIssue(condition.copy(state = "open"), true, owner -> repoName),
countIssue(condition.copy(state = "closed"), true, owner -> repoName),
countIssue(condition.copy(state = "open"), IssueSearchOption.PullRequests, owner -> repoName),
countIssue(condition.copy(state = "closed"), IssueSearchOption.PullRequests, owner -> repoName),
condition,
repository,
isEditable(repository),

View File

@@ -107,14 +107,14 @@ trait IssuesService {
* Returns the count of the search result against issues.
*
* @param condition the search condition
* @param onlyPullRequest if true then counts only pull request, false then counts both of issue and pull request.
* @param searchOption 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, onlyPullRequest: Boolean, repos: (String, String)*)(
def countIssue(condition: IssueSearchCondition, searchOption: IssueSearchOption, repos: (String, String)*)(
implicit s: Session
): Int = {
Query(searchIssueQuery(repos, condition, onlyPullRequest).length).first
Query(searchIssueQuery(repos, condition, searchOption).length).first
}
/**
@@ -132,7 +132,7 @@ trait IssuesService {
filterUser: Map[String, String]
)(implicit s: Session): Map[String, Int] = {
searchIssueQuery(Seq(owner -> repository), condition.copy(labels = Set.empty), false)
searchIssueQuery(Seq(owner -> repository), condition.copy(labels = Set.empty), IssueSearchOption.Issues)
.join(IssueLabels)
.on {
case t1 ~ t2 =>
@@ -170,7 +170,7 @@ trait IssuesService {
filterUser: Map[String, String]
)(implicit s: Session): Map[String, Int] = {
searchIssueQuery(Seq(owner -> repository), condition.copy(labels = Set.empty), false)
searchIssueQuery(Seq(owner -> repository), condition.copy(labels = Set.empty), IssueSearchOption.Issues)
.join(Priorities)
.on {
case t1 ~ t2 =>
@@ -223,7 +223,7 @@ trait IssuesService {
* Returns the search result against issues.
*
* @param condition the search condition
* @param pullRequest if true then returns only pull requests, false then returns only issues.
* @param searchOption 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
@@ -231,13 +231,13 @@ trait IssuesService {
*/
def searchIssue(
condition: IssueSearchCondition,
pullRequest: Boolean,
searchOption: IssueSearchOption,
offset: Int,
limit: Int,
repos: (String, String)*
)(implicit s: Session): List[IssueInfo] = {
// get issues and comment count and labels
val result = searchIssueQueryBase(condition, pullRequest, offset, limit, repos)
val result = searchIssueQueryBase(condition, searchOption, offset, limit, repos)
.joinLeft(IssueLabels)
.on { case t1 ~ t2 ~ i ~ t3 => t1.byIssue(t3.userName, t3.repositoryName, t3.issueId) }
.joinLeft(Labels)
@@ -288,7 +288,7 @@ trait IssuesService {
implicit s: Session
): List[(Issue, Account, Option[Account])] = {
// get issues and comment count and labels
searchIssueQueryBase(condition, false, offset, limit, repos)
searchIssueQueryBase(condition, IssueSearchOption.Issues, offset, limit, repos)
.join(Accounts)
.on { case t1 ~ t2 ~ i ~ t3 => t3.userName === t1.openedUserName }
.joinLeft(Accounts)
@@ -305,7 +305,7 @@ trait IssuesService {
implicit s: Session
): List[(Issue, Account, Int, PullRequest, Repository, Account, Option[Account])] = {
// get issues and comment count and labels
searchIssueQueryBase(condition, true, offset, limit, repos)
searchIssueQueryBase(condition, IssueSearchOption.PullRequests, offset, limit, repos)
.join(PullRequests)
.on { case t1 ~ t2 ~ i ~ t3 => t3.byPrimaryKey(t1.userName, t1.repositoryName, t1.issueId) }
.join(Repositories)
@@ -323,12 +323,12 @@ trait IssuesService {
private def searchIssueQueryBase(
condition: IssueSearchCondition,
pullRequest: Boolean,
searchOption: IssueSearchOption,
offset: Int,
limit: Int,
repos: Seq[(String, String)]
)(implicit s: Session) =
searchIssueQuery(repos, condition, pullRequest)
searchIssueQuery(repos, condition, searchOption)
.join(IssueOutline)
.on { (t1, t2) =>
t1.byIssue(t2.userName, t2.repositoryName, t2.issueId)
@@ -366,7 +366,11 @@ trait IssuesService {
/**
* Assembles query for conditional issue searching.
*/
private def searchIssueQuery(repos: Seq[(String, String)], condition: IssueSearchCondition, pullRequest: Boolean)(
private def searchIssueQuery(
repos: Seq[(String, String)],
condition: IssueSearchCondition,
searchOption: IssueSearchOption
)(
implicit s: Session
) =
Issues filter { t1 =>
@@ -380,7 +384,11 @@ trait IssuesService {
(t1.priorityId.? isEmpty, condition.priority == Some(None)) &&
(t1.assignedUserName.? isEmpty, condition.assigned == Some(None)) &&
(t1.openedUserName === condition.author.get.bind, condition.author.isDefined) &&
(t1.pullRequest === pullRequest.bind) &&
(searchOption match {
case IssueSearchOption.Issues => t1.pullRequest === false
case IssueSearchOption.PullRequests => t1.pullRequest === true
case IssueSearchOption.Both => t1.pullRequest === false || t1.pullRequest === true
}) &&
// Milestone filter
(Milestones filter { t2 =>
(t2.byPrimaryKey(t1.userName, t1.repositoryName, t1.milestoneId)) &&
@@ -927,6 +935,27 @@ object IssuesService {
param(request, "groups").map(_.split(",").toSet).getOrElse(Set.empty)
)
def apply(request: HttpServletRequest, milestone: String): IssueSearchCondition =
IssueSearchCondition(
param(request, "labels").map(_.split(",").toSet).getOrElse(Set.empty),
Some(Some(milestone)),
param(request, "priority").map {
case "none" => None
case x => Some(x)
},
param(request, "author"),
param(request, "assigned").map {
case "none" => None
case x => Some(x)
},
param(request, "mentioned"),
param(request, "state", Seq("open", "closed")).getOrElse("open"),
param(request, "sort", Seq("created", "comments", "updated", "priority")).getOrElse("created"),
param(request, "direction", Seq("asc", "desc")).getOrElse("desc"),
param(request, "visibility"),
param(request, "groups").map(_.split(",").toSet).getOrElse(Set.empty)
)
def page(request: HttpServletRequest) = {
PaginationHelper.page(param(request, "page"))
}
@@ -951,3 +980,11 @@ object IssuesService {
)
}
sealed trait IssueSearchOption
object IssueSearchOption {
case object Issues extends IssueSearchOption
case object PullRequests extends IssueSearchOption
case object Both extends IssueSearchOption
}

View File

@@ -318,8 +318,8 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
// Retrieve all issue count in the repository
val issueCount =
countIssue(IssueSearchCondition(state = "open"), false, owner -> repository) +
countIssue(IssueSearchCondition(state = "closed"), false, owner -> repository)
countIssue(IssueSearchCondition(state = "open"), IssueSearchOption.Issues, owner -> repository) +
countIssue(IssueSearchCondition(state = "closed"), IssueSearchOption.Issues, owner -> repository)
// Extract new commit and apply issue comment
val defaultBranch = repositoryInfo.repository.defaultBranch

View File

@@ -46,6 +46,7 @@
<form id="batcheditForm" method="POST">
<input type="hidden" name="value"/>
<input type="hidden" name="checked"/>
<input type="hidden" name="uri"/>
<input type="hidden" name="from" value="@target"/>
</form>
}

View File

@@ -32,7 +32,7 @@
<td style="padding-top: 15px; padding-bottom: 15px;">
<div class="milestone row">
<div class="col-md-4">
<a href="@helpers.url(repository)/issues?milestone=@helpers.urlEncode(milestone.title)&state=open" class="milestone-title">@milestone.title</a>
<a href="@helpers.url(repository)/milestone/@milestone.milestoneId" class="milestone-title">@milestone.title</a>
<div>
@if(milestone.closedDate.isDefined){
<span class="muted">Closed @gitbucket.core.helper.html.datetimeago(milestone.closedDate.get)</span>

View File

@@ -0,0 +1,121 @@
@(issues: List[gitbucket.core.service.IssuesService.IssueInfo],
page: Int,
openCount: Int,
closedCount: Int,
condition: gitbucket.core.service.IssuesService.IssueSearchCondition,
collaborators: List[String] = Nil,
milestones: List[gitbucket.core.model.Milestone] = Nil,
priorities: List[gitbucket.core.model.Priority] = Nil,
labels: List[gitbucket.core.model.Label] = Nil,
repository: Option[gitbucket.core.service.RepositoryService.RepositoryInfo] = None,
isManageable: Boolean = false)(implicit context: gitbucket.core.controller.Context)
@import gitbucket.core.view.helpers
@import gitbucket.core.service.IssuesService.IssueInfo
@*
@if(condition.nonEmpty){
<div style="width: 100%; display: inline-block;">
<a href="@gitbucket.core.service.IssuesService.IssueSearchCondition().toURL" class="header-link">
<i class="octicon octicon-x" ></i>
<span class="strong">Clear current search query, filters, and sorts</span>
</a>
</div>
}
*@
<table class="table table-bordered table-hover table-issues">
<thead>
<tr>
<th style="background-color: #eee;">
@if(isManageable){
<input type="checkbox" aria-label="Select all issues"/>
<span id="table-issues-batchedit">
@gitbucket.core.helper.html.dropdown("Mark as") {
<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>
}
@gitbucket.core.helper.html.dropdown("Label", filter = ("label", "Find Label...")) {
@labels.map { label =>
<li>
<a href="javascript:void(0);" class="toggle-label" data-id="@label.labelId">
<i class="octicon"></i>
<span class="label" style="background-color: #@label.color;">&nbsp;</span>
@label.labelName
</a>
</li>
}
}
@gitbucket.core.helper.html.dropdown("Assignee", filter = ("assignee", "Find Assignee...")) {
<li><a href="javascript:void(0);" class="toggle-assign" data-name=""><i class="octicon octicon-x"></i> Clear assignee</a></li>
@collaborators.map { collaborator =>
<li><a href="javascript:void(0);" class="toggle-assign" data-name="@collaborator"><i class="octicon"></i>@helpers.avatar(collaborator, 20) @collaborator</a></li>
}
}
</span>
}
</th>
</tr>
</thead>
<tbody>
@if(issues.isEmpty){
<tr>
<td style="padding: 20px; background-color: #eee; text-align: center;">
No issues and pull requests to show.
</td>
</tr>
}
@issues.map { case IssueInfo(issue, labels, milestone, priority, commentCount, commitStatus) => {
<tr>
<td style="padding-top: 12px; padding-bottom: 12px;">
@if(isManageable){
<input type="checkbox" value="@issue.issueId" aria-labelledby="issue_@(issue.issueId)_link"/>
}
@*
<i class="octicon octicon-issue-@(if(issue.closed) "closed" else "opened")" style="margin-right: 3px;"></i>
*@
@if(repository.isEmpty){
<a href="@context.path/@issue.userName/@issue.repositoryName">@issue.repositoryName</a>&nbsp;&#xFF65;
}
@if(issue.isPullRequest){
<a id="issue_@(issue.issueId)_link" href="@context.path/@issue.userName/@issue.repositoryName/pull/@issue.issueId" class="issue-title">
<i class="menu-icon octicon octicon-git-pull-request"></i>@issue.title
</a>
} else {
<a id="issue_@(issue.issueId)_link" href="@context.path/@issue.userName/@issue.repositoryName/issues/@issue.issueId" class="issue-title">
@if(issue.closed){<i class="menu-icon octicon octicon-issue-closed"></i>}else{<i class="menu-icon octicon octicon-issue-opened"></i>}@issue.title@issue.title
</a>
}
@gitbucket.core.issues.html.commitstatus(issue, commitStatus)
@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 small">
@issue.assignedUserName.map { userName =>
@helpers.avatar(userName, 20, tooltip = true)
}
@if(commentCount > 0){
<a href="@context.path/@issue.userName/@issue.repositoryName/issues/@issue.issueId" class="issue-comment-count">
<i class="octicon octicon-comment active"></i> @commentCount
</a>
} else {
<a href="@context.path/@issue.userName/@issue.repositoryName/issues/@issue.issueId" class="issue-comment-count" style="color: silver;">
<i class="octicon octicon-comment"></i> @commentCount
</a>
}
</span>
<div class="small muted" style="margin-left: 12px; margin-top: 2px;">
#@issue.issueId opened @gitbucket.core.helper.html.datetimeago(issue.registeredDate) by @helpers.user(issue.openedUserName, styleClass="username")
@priority.map(priority => priorities.filter(p => p.priorityName == priority).head).map { priority =>
<span style="margin: 20px;"><a href="@condition.copy(priority = Some(Some(priority.priorityName))).toURL" class="username"@if(!priority.description.isEmpty) { title="@priority.description.get" }><i class="octicon octicon-flame"></i>
<span class="issue-priority issue-priority-inline" style="background-color: #@priority.color; color: #@priority.fontColor;">@priority.priorityName</span></a></span>
}
@milestone.map { milestone =>
<span style="margin: 20px;"><a href="@condition.copy(milestone = Some(Some(milestone))).toURL" class="username"><i class="octicon octicon-milestone"></i> @milestone</a></span>
}
</div>
</td>
</tr>
}}
</tbody>
</table>
<div class="pull-right">
@gitbucket.core.helper.html.paginator(page, (if(condition.state == "open") openCount else closedCount), gitbucket.core.service.IssuesService.IssueLimit, 10, condition.toURL)
</div>

View File

@@ -0,0 +1,154 @@
@(state: String,
issues: List[gitbucket.core.service.IssuesService.IssueInfo],
page: Int,
collaborators: List[String],
priorities: List[gitbucket.core.model.Priority],
labels: List[gitbucket.core.model.Label],
condition: gitbucket.core.service.IssuesService.IssueSearchCondition,
milestones: List[(gitbucket.core.model.Milestone, Int, Int)],
repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
hasWritePermission: Boolean)(implicit context: gitbucket.core.controller.Context)
@import gitbucket.core.view.helpers
@gitbucket.core.html.main(s"Milestones - ${repository.owner}/${repository.name}"){
@gitbucket.core.html.menu("milestones", repository){
@milestones.map { case (milestone, openCount, closedCount) =>
<table class="table table-issues">
<tbody>
<tr>
<td style="padding-top: 15px; padding-bottom: 15px;">
<div class="milestone row">
<div class="col-md-4">
<a class="milestone-title">@milestone.title</a>
<div>
@if(milestone.closedDate.isDefined){
<span class="muted">Closed @gitbucket.core.helper.html.datetimeago(milestone.closedDate.get)</span>
} else {
@milestone.dueDate.map { dueDate =>
@if(helpers.isPast(dueDate)){
<i class="octicon octicon-alert" style="color:#BD2C00;"></i>
<span class="muted milestone-alert">Due by @helpers.date(dueDate)</span>
} else {
<span class="muted">Due by @helpers.date(dueDate)</span>
}
}.getOrElse {
<span class="muted">No due date</span>
}
}
</div>
</div>
<div class="col-md-8">
@gitbucket.core.issues.milestones.html.progress(openCount + closedCount, closedCount)
<div>
<div>
@if(closedCount == 0){
0%
} else {
@((closedCount.toDouble / (openCount + closedCount).toDouble * 100).toInt)%
} <span class="muted">complete</span> &nbsp;&nbsp;
@openCount <span class="muted">open</span> &nbsp;&nbsp;
@closedCount <span class="muted">closed</span>
</div>
<div class="milestone-menu">
@if(hasWritePermission){
<a href="@helpers.url(repository)/issues/milestones/@milestone.milestoneId/edit">Edit</a> &nbsp;&nbsp;
@if(milestone.closedDate.isDefined){
<a href="@helpers.url(repository)/issues/milestones/@milestone.milestoneId/open">Open</a> &nbsp;&nbsp;
} else {
<a href="@helpers.url(repository)/issues/milestones/@milestone.milestoneId/close">Close</a> &nbsp;&nbsp;
}
<a href="@helpers.url(repository)/issues/milestones/@milestone.milestoneId/delete" class="delete">Delete</a>
}
</div>
</div>
</div>
</div>
@milestone.description.map { description =>
<div class="milestone-description markdown-body">
@helpers.markdown(
markdown = description,
repository = repository,
branch = repository.repository.defaultBranch,
enableWikiLink = false,
enableRefsLink = false,
enableLineBreaks = true
)
</div>
}
</td>
</tr>
</tbody>
</table>
<ul class="nav nav-pills pull-left" style="line-height: 14px; margin-bottom: 10px;">
<li class="@if(condition.state == "open"){active}">
<a href="@context.path/@repository.owner/@repository.name/milestone/@milestone.milestoneId">Open <span class="badge">@openCount</span></a>
</li>
<li class="@if(condition.state == "closed"){active}">
<a href="@context.path/@repository.owner/@repository.name/milestone/@milestone.milestoneId?state=closed">Closed <span class="badge">@closedCount</span></a>
</li>
</ul>
@gitbucket.core.issues.milestones.html.listparts(issues, page, openCount, closedCount, condition, collaborators, milestones.map(p => p._1), priorities, labels, Some(repository), hasWritePermission)
@if(hasWritePermission){
<form id="batcheditForm" method="POST">
<input type="hidden" name="value"/>
<input type="hidden" name="checked"/>
<input type="hidden" name="uri" value="@context.path/@repository.owner/@repository.name/milestone/@milestone.milestoneId@if(condition.state=="closed"){?state=closed}">
<input type="hidden" name="from" value="milestone"/>
</form>
}
}
}
}
@if(hasWritePermission){
<script>
$(function(){
$('a.delete').click(function(){
return confirm('Once you delete this milestone, there is no going back.\nAre you sure?');
});
$('.table-issues input[type=checkbox]').change(function(){
var all = $('.table-issues input[type=checkbox][value]');
// check all
if($(this).val() == 'on'){
var flag = $(this).is(":checked");
all.each(function(){
$(this).prop("checked", flag);
});
}
var count = all.filter(':checked').length;
// whether all check
$('.table-issues input[type=checkbox]').filter(':first').prop("checked", count > 0 && count == all.length);
if(count == 0){
$('#table-issues-control').show();
$('#table-issues-batchedit').hide();
} else {
$('#batchedit-selected').text(count);
$('#table-issues-control').hide();
$('#table-issues-batchedit').show();
}
}).filter(':first').change();
var submitBatchEdit = function(action, value) {
var checked = $('.table-issues input[type=checkbox][value]').filter(':checked').map(function(){ return this.value; }).get().join();
var form = $('#batcheditForm');
form.find('input[name=value]').val(value);
form.find('input[name=checked]').val(checked);
form.attr('action', action);
form.submit();
};
$('a.toggle-state').click(function(){
submitBatchEdit('@helpers.url(repository)/issues/batchedit/state', $(this).data('id'));
});
$('a.toggle-label').click(function(){
submitBatchEdit('@helpers.url(repository)/issues/batchedit/label', $(this).data('id'));
});
$('a.toggle-assign').click(function(){
submitBatchEdit('@helpers.url(repository)/issues/batchedit/assign', $(this).data('name'));
});
});
</script>
}