* Added progressbar for issues (#1146). * Updated the generated index.css. Signed-off-by: modmew8 <modmew8@gmail.com> * Removed stored progress percentage and changed it to css calc. Also added the issue task progress to the user/dashboard/issues. Signed-off-by: modmew8 <modmew8@gmail.com> * Removed unnecessary blanks. Signed-off-by: modmew8 <modmew8@gmail.com> * Formatted the files correctly, fmt-check terminates now without errors. Signed-off-by: modmew8 <modmew8@gmail.com> * Removed variables, made computing the tasks on demand with precompiled regexp. Signed-off-by: modmew8 <modmew8@gmail.com>tags/v1.4.0-rc1
| @@ -7,6 +7,7 @@ package models | |||||
| import ( | import ( | ||||
| "fmt" | "fmt" | ||||
| "path" | "path" | ||||
| "regexp" | |||||
| "sort" | "sort" | ||||
| "strings" | "strings" | ||||
| @@ -54,6 +55,19 @@ type Issue struct { | |||||
| Reactions ReactionList `xorm:"-"` | Reactions ReactionList `xorm:"-"` | ||||
| } | } | ||||
| var ( | |||||
| issueTasksPat *regexp.Regexp | |||||
| issueTasksDonePat *regexp.Regexp | |||||
| ) | |||||
| const issueTasksRegexpStr = `(^\s*-\s\[[\sx]\]\s)|(\n\s*-\s\[[\sx]\]\s)` | |||||
| const issueTasksDoneRegexpStr = `(^\s*-\s\[[x]\]\s)|(\n\s*-\s\[[x]\]\s)` | |||||
| func init() { | |||||
| issueTasksPat = regexp.MustCompile(issueTasksRegexpStr) | |||||
| issueTasksDonePat = regexp.MustCompile(issueTasksDoneRegexpStr) | |||||
| } | |||||
| func (issue *Issue) loadRepo(e Engine) (err error) { | func (issue *Issue) loadRepo(e Engine) (err error) { | ||||
| if issue.Repo == nil { | if issue.Repo == nil { | ||||
| issue.Repo, err = getRepositoryByID(e, issue.RepoID) | issue.Repo, err = getRepositoryByID(e, issue.RepoID) | ||||
| @@ -741,6 +755,7 @@ func AddDeletePRBranchComment(doer *User, repo *Repository, issueID int64, branc | |||||
| func (issue *Issue) ChangeContent(doer *User, content string) (err error) { | func (issue *Issue) ChangeContent(doer *User, content string) (err error) { | ||||
| oldContent := issue.Content | oldContent := issue.Content | ||||
| issue.Content = content | issue.Content = content | ||||
| if err = UpdateIssueCols(issue, "content"); err != nil { | if err = UpdateIssueCols(issue, "content"); err != nil { | ||||
| return fmt.Errorf("UpdateIssueCols: %v", err) | return fmt.Errorf("UpdateIssueCols: %v", err) | ||||
| } | } | ||||
| @@ -818,6 +833,16 @@ func (issue *Issue) ChangeAssignee(doer *User, assigneeID int64) (err error) { | |||||
| return nil | return nil | ||||
| } | } | ||||
| // GetTasks returns the amount of tasks in the issues content | |||||
| func (issue *Issue) GetTasks() int { | |||||
| return len(issueTasksPat.FindAllStringIndex(issue.Content, -1)) | |||||
| } | |||||
| // GetTasksDone returns the amount of completed tasks in the issues content | |||||
| func (issue *Issue) GetTasksDone() int { | |||||
| return len(issueTasksDonePat.FindAllStringIndex(issue.Content, -1)) | |||||
| } | |||||
| // NewIssueOptions represents the options of a new issue. | // NewIssueOptions represents the options of a new issue. | ||||
| type NewIssueOptions struct { | type NewIssueOptions struct { | ||||
| Repo *Repository | Repo *Repository | ||||
| @@ -1491,6 +1491,20 @@ | |||||
| .desc { | .desc { | ||||
| padding-top: 5px; | padding-top: 5px; | ||||
| color: #999; | color: #999; | ||||
| .progress-bar { | |||||
| width: 80px; | |||||
| height: 6px; | |||||
| display: inline-block; | |||||
| background-color: #eee; | |||||
| overflow: hidden; | |||||
| border-radius: 3px; | |||||
| vertical-align: middle !important; | |||||
| .progress { | |||||
| background-color: #ccc; | |||||
| display: block; | |||||
| height: 100%; | |||||
| } | |||||
| } | |||||
| a.milestone { | a.milestone { | ||||
| padding-left: 5px; | padding-left: 5px; | ||||
| color: #999!important; | color: #999!important; | ||||
| @@ -200,6 +200,11 @@ | |||||
| <p class="desc"> | <p class="desc"> | ||||
| {{$.i18n.Tr "repo.issues.opened_by" $timeStr .Poster.HomeLink .Poster.Name | Safe}} | {{$.i18n.Tr "repo.issues.opened_by" $timeStr .Poster.HomeLink .Poster.Name | Safe}} | ||||
| {{$tasks := .GetTasks}} | |||||
| {{if gt $tasks 0}} | |||||
| {{$tasksDone := .GetTasksDone}} | |||||
| <span class="octicon octicon-checklist"></span> {{$tasksDone}} / {{$tasks}} <span class="progress-bar"><span class="progress" style="width:calc(100% * {{$tasksDone}} / {{$tasks}});"></span></span> | |||||
| {{end}} | |||||
| {{if .Milestone}} | {{if .Milestone}} | ||||
| <a class="milestone" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{.Milestone.ID}}&assignee={{$.AssigneeID}}"> | <a class="milestone" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{.Milestone.ID}}&assignee={{$.AssigneeID}}"> | ||||
| <span class="octicon octicon-milestone"></span> {{.Milestone.Name}} | <span class="octicon octicon-milestone"></span> {{.Milestone.Name}} | ||||
| @@ -87,6 +87,11 @@ | |||||
| <img class="ui avatar image" src="{{.Assignee.RelAvatarLink}}"> | <img class="ui avatar image" src="{{.Assignee.RelAvatarLink}}"> | ||||
| </a> | </a> | ||||
| {{end}} | {{end}} | ||||
| {{$tasks := .GetTasks}} | |||||
| {{if gt $tasks 0}} | |||||
| {{$tasksDone := .GetTasksDone}} | |||||
| <span class="octicon octicon-checklist"></span> {{$tasksDone}} / {{$tasks}} <span class="progress-bar"><span class="progress" style="width:calc(100% * {{$tasksDone}} / {{$tasks}});"></span></span> | |||||
| {{end}} | |||||
| </p> | </p> | ||||
| </li> | </li> | ||||
| {{end}} | {{end}} | ||||