[Notice] Announcing the End of Demo Server [Read me]

issue: issue tracker WIP
@1b4ad978bf2a58b8fdf69469c33def2da9243014
--- app/controllers/IssueApp.java
+++ app/controllers/IssueApp.java
... | ... | @@ -66,6 +66,7 @@ |
66 | 66 |
return badRequest(newIssue.render(issueForm.errors().toString(), issueForm, project)); |
67 | 67 |
} else { |
68 | 68 |
Issue newIssue = issueForm.get(); |
69 |
+ newIssue.date = JodaDateUtil.now(); |
|
69 | 70 |
newIssue.authorId = UserApp.currentUser().id; |
70 | 71 |
newIssue.authorName = UserApp.currentUser().name; |
71 | 72 |
newIssue.project = project; |
... | ... | @@ -91,7 +92,7 @@ |
91 | 92 |
Issue targetIssue = Issue.findById(id); |
92 | 93 |
Form<Issue> editForm = new Form<Issue>(Issue.class).fill(targetIssue); |
93 | 94 |
Project project = ProjectApp.getProject(userName, projectName); |
94 |
- return ok(editIssue.render("title.editIssue", editForm, id, project)); |
|
95 |
+ return ok(editIssue.render("title.editIssue", editForm, targetIssue, project)); |
|
95 | 96 |
} |
96 | 97 |
|
97 | 98 |
public static Result updateIssue(String userName, String projectName, Long id) throws IOException { |
... | ... | @@ -104,6 +105,13 @@ |
104 | 105 |
issue.id = id; |
105 | 106 |
issue.date = Issue.findById(id).date; |
106 | 107 |
issue.project = project; |
108 |
+ String[] labelIds = request().body().asMultipartFormData().asFormUrlEncoded().get("labelIds[]"); |
|
109 |
+ if (labelIds != null) { |
|
110 |
+ for (String labelId: labelIds) { |
|
111 |
+ issue.labels.add(IssueLabel.findById(Long.parseLong(labelId))); |
|
112 |
+ } |
|
113 |
+ } |
|
114 |
+ |
|
107 | 115 |
Issue.edit(issue); |
108 | 116 |
|
109 | 117 |
// Attach the files in the current user's temporary storage. |
--- app/models/Issue.java
+++ app/models/Issue.java
... | ... | @@ -83,6 +83,7 @@ |
83 | 83 |
public Set<IssueLabel> labels; |
84 | 84 |
|
85 | 85 |
public Issue(String title) { |
86 |
+ this.title = title; |
|
86 | 87 |
this.date = JodaDateUtil.now(); |
87 | 88 |
} |
88 | 89 |
|
... | ... | @@ -90,8 +91,13 @@ |
90 | 91 |
return JodaDateUtil.ago(this.date); |
91 | 92 |
} |
92 | 93 |
|
93 |
- public String reporterName() { |
|
94 |
- return User.findNameById(this.authorId); |
|
94 |
+ public String getAuthorName() { |
|
95 |
+ if (authorName == null) { |
|
96 |
+ if (authorId == null) return null; |
|
97 |
+ else return User.findById(this.authorId).name; |
|
98 |
+ } else { |
|
99 |
+ return authorName; |
|
100 |
+ } |
|
95 | 101 |
} |
96 | 102 |
|
97 | 103 |
/** |
... | ... | @@ -99,8 +105,7 @@ |
99 | 105 |
* 있을듯. |
100 | 106 |
*/ |
101 | 107 |
public String assigneeName() { |
102 |
- |
|
103 |
- return (this.assigneeId != null ? User.findNameById(this.assigneeId) : "issue.noAssignee"); |
|
108 |
+ return (this.assigneeId != null ? User.findNameById(this.assigneeId) : null); |
|
104 | 109 |
} |
105 | 110 |
|
106 | 111 |
/** |
--- app/utils/PlayServletRequest.java
+++ app/utils/PlayServletRequest.java
... | ... | @@ -45,9 +45,9 @@ |
45 | 45 |
public class PlayServletRequest implements HttpServletRequest { |
46 | 46 |
|
47 | 47 |
private String characterEncoding; |
48 |
- private Request request; |
|
48 |
+ private final Request request; |
|
49 | 49 |
Map<String, Object> attributes = new HashMap<String, Object>(); |
50 |
- private HttpSession httpSession; |
|
50 |
+ private final HttpSession httpSession; |
|
51 | 51 |
|
52 | 52 |
public PlayServletRequest(Request request, HttpSession httpSession, String pathInfo) { |
53 | 53 |
this.request = request; |
... | ... | @@ -57,7 +57,7 @@ |
57 | 57 |
|
58 | 58 |
/** |
59 | 59 |
* The set of SimpleDateFormat formats to use in getDateHeader(). |
60 |
- * |
|
60 |
+ * |
|
61 | 61 |
* Notice that because SimpleDateFormat is not thread-safe, we can't declare |
62 | 62 |
* formats[] as a static variable. |
63 | 63 |
*/ |
... | ... | @@ -65,7 +65,7 @@ |
65 | 65 |
new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US), |
66 | 66 |
new SimpleDateFormat("EEEEEE, dd-MMM-yy HH:mm:ss zzz", Locale.US), |
67 | 67 |
new SimpleDateFormat("EEE MMMM d HH:mm:ss yyyy", Locale.US) }; |
68 |
- private String pathInfo; |
|
68 |
+ private final String pathInfo; |
|
69 | 69 |
|
70 | 70 |
@Override |
71 | 71 |
public AsyncContext getAsyncContext() { |
--- app/utils/TemplateHelper.scala
+++ app/utils/TemplateHelper.scala
... | ... | @@ -4,6 +4,7 @@ |
4 | 4 |
import org.joda.time.DateTimeConstants |
5 | 5 |
import play.i18n.Messages |
6 | 6 |
import controllers.routes |
7 |
+import java.security.MessageDigest |
|
7 | 8 |
|
8 | 9 |
object TemplateHelper { |
9 | 10 |
|
... | ... | @@ -51,4 +52,9 @@ |
51 | 52 |
if (play.Play.isProd) minified = ".min" |
52 | 53 |
routes.Assets.at(base + "/" + name + minified + "." + _type).toString |
53 | 54 |
} |
54 |
-}(No newline at end of file) |
|
55 |
+ |
|
56 |
+ def urlToPicture(email: String, size: Int = 34) = { |
|
57 |
+ "http://www.gravatar.com/avatar/" + MessageDigest.getInstance("MD5").digest(email.toLowerCase.getBytes).map("%02x".format(_)).mkString + "?s=" + size |
|
58 |
+ } |
|
59 |
+ |
|
60 |
+} |
--- app/views/issue/editIssue.scala.html
+++ app/views/issue/editIssue.scala.html
... | ... | @@ -1,7 +1,7 @@ |
1 |
-@(title:String, issueForm:Form[Issue], issueId:Long, project:Project) |
|
1 |
+@(title:String, issueForm:Form[Issue], issue:Issue, project:Project) |
|
2 | 2 |
@import helper._ |
3 | 3 |
@import scala.collection.mutable.Map |
4 |
-@implicitFieldConstructor = @{ FieldConstructor(twitterBootstrapInput.render) } |
|
4 |
+@implicitFieldConstructor = @{ FieldConstructor(twitterBootstrapInput.render) } |
|
5 | 5 |
@import models.enumeration.Resource |
6 | 6 |
|
7 | 7 |
@isVisible(resource: models.enumeration.Resource)(content: => Html) = @{ |
... | ... | @@ -11,98 +11,64 @@ |
11 | 11 |
} |
12 | 12 |
|
13 | 13 |
@main(Messages(title), project) { |
14 |
+<style> |
|
15 |
+@@IMPORT url("/assets/stylesheets/issue.css"); |
|
16 |
+</style> |
|
14 | 17 |
|
15 |
-<!-- FIXEME |
|
16 |
-(1) 수정했을 시에, 수정시간으로 업데이트 되는 것이 아니라, 본 글이 원래 작성된 시간을 그대로 가지고 있을 것! |
|
17 |
-(2) 매니저 및 멤버가 해당 이슈를 수정시에, 작성자가 변하지 않아야 함. |
|
18 |
---> |
|
19 |
- @form(action = routes.IssueApp.updateIssue(project.owner, project.name, issueId), |
|
20 |
- 'enctype -> "multipart/form-data", |
|
21 |
- 'class -> "form-horizontal"){ |
|
18 |
+<div class="page-header"> |
|
19 |
+ <h1>@Messages(title)</h1> |
|
20 |
+</div> |
|
21 |
+ |
|
22 |
+ @form(action = routes.IssueApp.updateIssue(project.owner, project.name, issue.id), |
|
23 |
+ 'id -> "issue-form", |
|
24 |
+ 'enctype -> "multipart/form-data" |
|
25 |
+ ) { |
|
22 | 26 |
<fieldset> |
23 |
- @inputText( |
|
24 |
- issueForm("title"), |
|
25 |
- '_showConstraints -> false, |
|
26 |
- '_label-> Messages("post.new.title"), |
|
27 |
- 'class -> "input-xxlarge") |
|
28 |
- |
|
29 |
- @textarea( |
|
30 |
- issueForm("body"), |
|
31 |
- '_showConstraints -> false, |
|
32 |
- '_label-> Messages("post.new.contents"), |
|
27 |
+ @inputText( |
|
28 |
+ issueForm("title"), |
|
29 |
+ '_showConstraints -> false, |
|
30 |
+ '_label-> null, |
|
31 |
+ 'class -> "title", |
|
32 |
+ 'onkeypress -> "return event.keyCode !== 13" |
|
33 |
+ ) |
|
34 |
+ @textarea( |
|
35 |
+ issueForm("body"), |
|
36 |
+ '_showConstraints -> false, |
|
37 |
+ '_label-> null, |
|
33 | 38 |
'rows -> 16, |
34 |
- 'class -> "input-xxlarge textbody", |
|
39 |
+ 'class -> "textbody", |
|
35 | 40 |
'resourceType -> Resource.ISSUE_POST, |
36 | 41 |
'markdown -> true, |
37 |
- 'resourceId -> issueId) |
|
38 |
- |
|
42 |
+ 'resourceId -> issue.id) |
|
39 | 43 |
</fieldset> |
40 |
- </br></br> |
|
41 |
- |
|
42 |
- <fieldset> |
|
43 |
- <div class="well"> |
|
44 |
- <legend><b>@Messages("issue.new.environment")</b></legend> |
|
45 |
- <div class="well form-inline"> |
|
46 |
- @selectEx( |
|
47 |
- issueForm("osType"), |
|
48 |
- options(Issue.osTypes), |
|
49 |
- '_label-> Messages("issue.new.environment.osType"), |
|
50 |
- '_default -> Messages("issue.new.selectDefault.osType"), |
|
51 |
- '_showConstraints -> false) |
|
52 |
- @selectEx( |
|
53 |
- issueForm("browserType"), |
|
54 |
- options(Issue.browserTypes), |
|
55 |
- '_label-> Messages("issue.new.environment.browserType"), |
|
56 |
- '_default -> Messages("issue.new.selectDefault.browserType"), |
|
57 |
- '_showConstraints -> false) |
|
58 |
- @selectEx( |
|
59 |
- issueForm("dbmsType"), |
|
60 |
- options(Issue.dbmsTypes), |
|
61 |
- '_label-> Messages("issue.new.environment.dbmsType"), |
|
62 |
- '_default -> Messages("issue.new.selectDefault.dbmsType"), |
|
63 |
- '_showConstraints -> false) |
|
64 |
- </div> |
|
65 |
- </div> |
|
66 |
- </fieldset> |
|
67 |
- |
|
68 |
- |
|
69 |
- |
|
70 |
- @if(ProjectUser.isMember(session.get("userId").toLong, project.id)){ |
|
71 |
- <fieldset> |
|
72 |
- <div class="well"> |
|
73 |
- <legend><b>@Messages("issue.new.detailInfo")</b></legend> |
|
74 |
- <div class="well form-inline"> |
|
75 |
- @isVisible(models.enumeration.Resource.ISSUE_CATEGORY) { |
|
76 |
- @selectEx( |
|
77 |
- issueForm("issueType"), |
|
78 |
- options(Issue.issueTypes), |
|
79 |
- '_label-> Messages("issue.new.detailInfo.issueType"), |
|
80 |
- '_default -> Messages("issue.new.selectDefault.issueType"), |
|
81 |
- '_showConstraints -> false) |
|
82 |
- } |
|
83 |
- @isVisible(models.enumeration.Resource.ISSUE_ASSIGNEE) { |
|
84 |
- @select( |
|
85 |
- issueForm("assigneeId"), |
|
86 |
- options(ProjectUser.options(project.id)), |
|
87 |
- '_label-> Messages("issue.new.detailInfo.assignee"), |
|
88 |
- '_default -> Messages("issue.new.selectDefault.assignee"), |
|
89 |
- '_showConstraints -> false) |
|
90 |
- } |
|
91 |
- @isVisible(models.enumeration.Resource.ISSUE_MILESTONE) { |
|
92 |
- @select( |
|
93 |
- issueForm("milestoneId"), |
|
94 |
- options(Milestone.options(project.id)), |
|
95 |
- '_label-> Messages("issue.new.detailInfo.milestone"), |
|
96 |
- '_default -> Messages("issue.new.selectDefault.milestone"), |
|
97 |
- '_showConstraints -> false) |
|
98 |
- } |
|
99 |
- </div> |
|
100 |
- </div> |
|
101 |
- </fieldset> |
|
102 |
- } |
|
103 |
- <input type="hidden" value="@issueForm("authorId").value" name="authorId"/> |
|
104 |
- <input type="submit" class="btn" value=@Messages("button.save") /> |
|
105 |
- <input type="reset" class="btn" value=@Messages("button.reset") /> |
|
44 |
+ |
|
45 |
+ <!-- issue.label js module appends a label selector here. --> |
|
46 |
+ <fieldset class="labels"> |
|
47 |
+ @isVisible(models.enumeration.Resource.ISSUE_ASSIGNEE) { |
|
48 |
+ @select( |
|
49 |
+ issueForm("assigneeId"), |
|
50 |
+ options(ProjectUser.options(project.id)), |
|
51 |
+ '_label-> Messages("issue.new.detailInfo.assignee"), |
|
52 |
+ '_default -> Messages("issue.new.selectDefault.assignee"), |
|
53 |
+ '_showConstraints -> false) |
|
54 |
+ } |
|
55 |
+ @isVisible(models.enumeration.Resource.ISSUE_MILESTONE) { |
|
56 |
+ @select( |
|
57 |
+ issueForm("milestoneId"), |
|
58 |
+ options(Milestone.options(project.id)), |
|
59 |
+ '_label-> Messages("issue.new.detailInfo.milestone"), |
|
60 |
+ '_default -> Messages("issue.new.selectDefault.milestone"), |
|
61 |
+ '_showConstraints -> false) |
|
62 |
+ } |
|
63 |
+ </fieldset> |
|
64 |
+ |
|
65 |
+ <input type="hidden" value="@issueForm("authorId").value" name="authorId"/> |
|
66 |
+ <div class="row pull-right"> |
|
67 |
+ <div class="actions"> |
|
68 |
+ <input type="submit" class="btn btn-primary" value="@Messages("button.save")"> |
|
69 |
+ <a href="@routes.IssueApp.issues(project.owner, project.name, "all")" class="btn">@Messages("button.cancel")</a> |
|
70 |
+ </div> |
|
71 |
+ </div> |
|
106 | 72 |
} |
107 | 73 |
|
108 | 74 |
@board.postVaildate() |
... | ... | @@ -110,5 +76,12 @@ |
110 | 76 |
@views.html.markdown() |
111 | 77 |
<script type="text/javascript"> |
112 | 78 |
nforge.require('shortcut.submit'); |
79 |
+ nforge.require('issue.label', '@routes.IssueLabelApp.getAll(project.owner, project.name)', '@routes.IssueLabelApp.post(project.owner, project.name)', {editable: false}, function() { |
|
80 |
+ @for(label <- issue.labels) { |
|
81 |
+ $('button.issue-label[labelId="@label.id"]').css('background-color', '@label.color'); |
|
82 |
+ $('button.issue-label[labelId="@label.id"]').css('color', contrasted_color('@label.color')); |
|
83 |
+ $('.labels button.btn[labelId="@label.id"]').addClass('active'); |
|
84 |
+ } |
|
85 |
+ }); |
|
113 | 86 |
</script> |
114 | 87 |
} |
--- app/views/issue/issue.scala.html
+++ app/views/issue/issue.scala.html
... | ... | @@ -1,9 +1,13 @@ |
1 |
-@(title:String, issue:Issue, issueForm:Form[Issue], commentForm:Form[IssueComment],project:Project) |
|
2 |
-@import helper._ |
|
1 |
+@(title:String, issue:Issue, issueForm:Form[Issue], commentForm:Form[IssueComment],project:Project) |
|
2 |
+@import helper._ |
|
3 | 3 |
@import scala.collection.mutable.Map |
4 | 4 |
@import models.enumeration.Resource |
5 |
+@import models.Milestone |
|
6 |
+@import java.text.SimpleDateFormat |
|
7 |
+@import java.security.MessageDigest |
|
8 |
+@import utils.TemplateHelper._ |
|
5 | 9 |
|
6 |
-@implicitFieldConstructor = @{ FieldConstructor(twitterBootstrapInput.render) } |
|
10 |
+@implicitFieldConstructor = @{ FieldConstructor(twitterBootstrapInput.render) } |
|
7 | 11 |
|
8 | 12 |
@isVisible(resource: models.enumeration.Resource)(content: => Html) = @{ |
9 | 13 |
roleCheck(session.get("userId"), project.id, resource, models.enumeration.Operation.EDIT, issue.id){ |
... | ... | @@ -15,168 +19,147 @@ |
15 | 19 |
@@IMPORT url("/assets/stylesheets/issue.css"); |
16 | 20 |
</style> |
17 | 21 |
|
18 |
-<div class="row"> |
|
19 |
- <div class="span2 offset10"> |
|
20 |
- <div id="board_btn_panel"> |
|
21 |
- @isVisible(models.enumeration.Resource.ISSUE_POST) { |
|
22 |
- <a class="btn btn-primary" href="@routes.IssueApp.editIssue(project.owner, project.name, issue.id)">@Messages("button.edit")</a> <a |
|
23 |
- data-toggle="modal" href="#deleteIssueConfirm" class="btn">@Messages("button.delete")</a> |
|
24 |
- } |
|
25 |
- <a class="btn" href="@routes.IssueApp.issues(project.owner, project.name,"open")">@Messages("button.list")</a> |
|
26 |
- </div> |
|
27 |
- </div> |
|
22 |
+<div class="page-padding"> |
|
23 |
+ <ul class="breadcrumb page-navi pull-right"> |
|
24 |
+ <li><a href="#" class="icon-home"></a><span class="divider">></span></li> |
|
25 |
+ <li><a href="#">project home</a><span class="divider">></span></li> |
|
26 |
+ <li class="active">Issue</li> |
|
27 |
+ </ul> |
|
28 |
+ |
|
29 |
+ <ul class="breadcrumb project-name"> |
|
30 |
+ <li><a href="#">@project.owner</a><span class="divider">/</span></li> |
|
31 |
+ <li class="active"><a href="#">@project.name</a></li> |
|
32 |
+ </ul> |
|
28 | 33 |
</div> |
34 |
+ |
|
29 | 35 |
<!-- 메인 글 --> |
30 |
-<div> |
|
31 |
- <div class="row"> |
|
32 |
- <div class="span1"> |
|
33 |
- <span class="badge">#@issue.id</span></br> |
|
34 |
- <span class="badge">@Messages(issue.state.state)</span> |
|
35 |
- </div> |
|
36 |
+<article> |
|
36 | 37 |
|
37 |
- <div class="span11"> |
|
38 |
- <h1>@issue.title</h1> |
|
39 |
- <p>@for(label <- issue.labels) { <button class="issue-label" labelId="@label.id">@label.name</button> } </p> |
|
40 |
- <div> |
|
41 |
- <span>@issue.reporterName이 @agoString(issue.ago())에 작성함</span> | |
|
42 |
- <span>@issue.date</span> |
|
43 |
- </div> |
|
44 |
- </div> |
|
45 |
- </div> |
|
46 |
- <div class="row"> |
|
47 |
- <div class="span1"> |
|
48 |
- <img class="pull-right" src="http://placehold.it/50x50" /> |
|
49 |
- </div> |
|
50 |
- <div class="span11"> |
|
51 |
- <div> |
|
52 |
- <div id="body" markdown resourceType=@Resource.ISSUE_POST resourceId="@issue.id">@issue.body</div> |
|
53 |
- </div> |
|
54 |
- </div> |
|
55 |
- <a class="btn pull-right" href=""><i class="icon-ok"></i>@Messages("button.autoNotification")</a> |
|
56 |
- </div> |
|
57 |
- </br></br> |
|
38 |
+ <hgroup class="row-fluid"> |
|
39 |
+ <div class="span1"> |
|
40 |
+ <img class="user-picture" src="@urlToPicture(User.findById(issue.authorId).email, 34)" /> |
|
41 |
+ </div> |
|
42 |
+ <div class="info"> |
|
43 |
+ <p> |
|
44 |
+ <span class="title">@issue.title</span> |
|
45 |
+ <span class="pull-right"> |
|
46 |
+ <span>@issue.id</span> |
|
47 |
+ <span class="badge badge-info">@Messages(issue.state.state)</span> |
|
48 |
+ </span> |
|
49 |
+ </p> |
|
50 |
+ <p class="author">by <a href="/users/@issue.authorId">@Option(issue.getAuthorName).orElse(Option(Messages("issue.noAuthor"))).get</a> @agoString(issue.ago)</p> |
|
51 |
+ </div> |
|
52 |
+ </hgroup> |
|
58 | 53 |
|
59 |
- @if(session.get("userId") != null && ProjectUser.isMember(session.get("userId").toLong, project.id)){ |
|
60 |
- |
|
61 |
- @form(action = routes.IssueApp.updateIssue(project.owner, project.name, issue.id), |
|
62 |
- 'enctype -> "multipart/form-data", |
|
63 |
- 'class -> "form-horizontal") { |
|
54 |
+ <div class="row issue-info-group"> |
|
55 |
+ <div class="span4 issue-info"> |
|
56 |
+ <p class="assignee">@if(issue.assigneeName != null) { |
|
57 |
+ <span class="assignee-name">@issue.assigneeName</span> |
|
58 |
+ } else { |
|
59 |
+ <span class="no-assignee">@Messages("issue.noAssignee")</span> |
|
60 |
+ } |
|
61 |
+ </p> |
|
62 |
+ <p class="issue-etc-info"> |
|
63 |
+ Hit <span class="number">0</span> |
|
64 |
+ | Comment <span class="number">@issue.numOfComments</span> |
|
65 |
+ | Like <span class="number">0</span> |
|
66 |
+ @if(issue.milestoneId != null) { |
|
67 |
+ | Milestone <span class="number">@Milestone.findById(issue.milestoneId).title</span> |
|
68 |
+ } |
|
69 |
+ </p> |
|
70 |
+ </div> |
|
71 |
+ <div class="span5"> |
|
72 |
+ <p class="pull-right">@for(label <- issue.labels) { <button class="issue-label" labelId="@label.id">@label.name</button> } </p> |
|
73 |
+ </div> |
|
74 |
+ </div> |
|
64 | 75 |
|
65 |
- <!-- issue.label module make this as label selector --> |
|
66 |
- <fieldset class="labels"></fieldset> |
|
76 |
+ <div class="body" markdown resourceType=@Resource.ISSUE_POST resourceId="@issue.id">@issue.body</div> |
|
77 |
+</article> |
|
67 | 78 |
|
68 |
- <fieldset> |
|
69 |
- <div class="well"> |
|
70 |
- <legend><b>@Messages("issue.new.detailInfo")</b></legend> |
|
71 |
- <div class="well form-inline"> |
|
72 |
- @isVisible(models.enumeration.Resource.ISSUE_ASSIGNEE) { |
|
73 |
- @select( |
|
74 |
- issueForm("assigneeId"), |
|
75 |
- options(ProjectUser.options(project.id)), |
|
76 |
- '_label-> Messages("issue.new.detailInfo.assignee"), |
|
77 |
- '_default -> Messages("issue.new.selectDefault.assignee"), |
|
78 |
- '_showConstraints -> false) |
|
79 |
- } |
|
80 |
- @isVisible(models.enumeration.Resource.ISSUE_MILESTONE) { |
|
81 |
- @select( |
|
82 |
- issueForm("milestoneId"), |
|
83 |
- options(Milestone.options(project.id)), |
|
84 |
- '_label-> Messages("issue.new.detailInfo.milestone"), |
|
85 |
- '_default -> Messages("issue.new.selectDefault.milestone"), |
|
86 |
- '_showConstraints -> false) |
|
87 |
- } |
|
88 |
- </div> |
|
89 |
- </div> |
|
90 |
- </fieldset> |
|
91 |
- <input type="hidden" value="@issue.title" name="title"/> |
|
92 |
- <input type="hidden" value="@issue.body" name="body"/> |
|
93 |
- <input type="hidden" value="@issue.authorId" name="authorId"/> |
|
94 |
- <div class = "btn pull-right"> |
|
95 |
- <input type="submit" value=@Messages("issue.committer.edit") /> |
|
96 |
- <!-- <input type="button" id=makeFinished value="이슈 해결" /> --> |
|
97 |
- </div> |
|
98 |
- } |
|
99 |
- } |
|
100 |
- |
|
101 |
-</div> |
|
102 | 79 |
<!-- 여기서 부터 덧글 --> |
103 |
-@for(comment <- issue.comments){ |
|
104 |
-<br/> |
|
105 |
-<div class="row"> |
|
106 |
- <div class="span1"> |
|
107 |
- <img class="pull-right" src="http://placehold.it/50x50" /> |
|
108 |
- </div> |
|
109 |
- <div class="span11"> |
|
110 |
- <div> |
|
111 |
- <span>@comment.authorName이 @agoString(comment.ago())에 작성함</span>| |
|
112 |
- <span>@comment.date</span> |
|
113 |
- </div> |
|
114 |
- @roleCheck(session.get("userId"), project.id, models.enumeration.Resource.ISSUE_COMMENT, models.enumeration.Operation.EDIT, comment.id) { |
|
115 |
- <div class="pull-right"> |
|
116 |
- <a data-toggle="modal" href='#deleteCommentConfirm'><i class="icon-remove"> </i> |
|
117 |
- </div> |
|
118 |
- |
|
119 |
- <div class="modal hide" id="deleteCommentConfirm"> |
|
120 |
- <div class="modal-header"> |
|
121 |
- <button type="button" class="close" data-dismiss="modal">×</button> |
|
122 |
- <h3>@Messages("issue.delete.window")</h3> |
|
123 |
- </div> |
|
124 |
- <div class="modal-body"> |
|
125 |
- <p>@Messages("issue.comment.delete.confirm")</p> |
|
126 |
- </div> |
|
127 |
- <div class="modal-footer"> |
|
128 |
- <a class="btn btn-danger" |
|
129 |
- href="@routes.IssueApp.deleteComment(project.owner, project.name, issue.id, comment.id)">@Messages("button.yes")</a> |
|
130 |
- <a href="#" class="btn" data-dismiss="modal">@Messages("button.no")</a> |
|
131 |
- </div> |
|
80 |
+<div class="comment-area"> |
|
81 |
+<p class="info">Comments <span class="number">@issue.numOfComments</span></p> |
|
82 |
+@if(issue.comments.size > 0) { |
|
83 |
+<div class="comments"> |
|
84 |
+ @for(comment <- issue.comments){ |
|
85 |
+ <div class="comment row-fluid"> |
|
86 |
+ <div class="span1"><img class="user-picture" src="@urlToPicture(User.findById(comment.authorId).email, 34)" /></div> |
|
87 |
+ <div class="span11"> |
|
88 |
+ <div> |
|
89 |
+ <span class="author">@comment.authorName</span> |
|
90 |
+ <span class="date">@comment.date</span> |
|
91 |
+ </div> |
|
92 |
+ @roleCheck(session.get("userId"), project.id, models.enumeration.Resource.ISSUE_COMMENT, models.enumeration.Operation.EDIT, comment.id) { |
|
93 |
+ <div class="pull-right"> |
|
94 |
+ <a data-toggle="modal" href='#deleteCommentConfirm'><i class="icon-remove"> </i> |
|
95 |
+ </div> |
|
96 |
+ |
|
97 |
+ <div class="modal hide" id="deleteCommentConfirm"> |
|
98 |
+ <div class="modal-header"> |
|
99 |
+ <button type="button" class="close" data-dismiss="modal">×</button> |
|
100 |
+ <h3>@Messages("issue.delete.window")</h3> |
|
132 | 101 |
</div> |
133 |
- } |
|
134 |
- <div markdown resourceType=@Resource.ISSUE_COMMENT resourceId=@comment.id>@comment.contents</div> |
|
135 |
- </div> |
|
102 |
+ <div class="modal-body"><p>@Messages("issue.comment.delete.confirm")</p></div> |
|
103 |
+ <div class="modal-footer"> |
|
104 |
+ <a class="btn btn-danger" |
|
105 |
+ href="@routes.IssueApp.deleteComment(project.owner, project.name, issue.id, comment.id)">@Messages("button.yes")</a> |
|
106 |
+ <a href="#" class="btn" data-dismiss="modal">@Messages("button.no")</a> |
|
107 |
+ </div> |
|
108 |
+ </div> |
|
109 |
+ } |
|
110 |
+ <div markdown resourceType=@Resource.ISSUE_COMMENT resourceId=@comment.id>@comment.contents</div> |
|
111 |
+ </div> |
|
112 |
+ </div> |
|
113 |
+ } |
|
136 | 114 |
</div> |
137 | 115 |
} |
116 |
+ |
|
138 | 117 |
<!-- 댓글 입력 폼 --> |
139 |
-<br /> |
|
140 |
-<div class="row pull-left"> |
|
141 |
- @form(routes.IssueApp.saveComment(project.owner, project.name, issue.id), |
|
142 |
- 'enctype -> "multipart/form-data", |
|
143 |
- 'class -> "form-horizontal"){ |
|
144 |
- <div class="span12"> |
|
145 |
- @textarea(commentForm("contents"), |
|
146 |
- '_label-> Messages("post.new.contents"), |
|
147 |
- 'rows -> 5, |
|
148 |
- 'markdown -> true, |
|
149 |
- 'class -> "input-xxlarge textbody") |
|
150 |
- </div> |
|
151 |
- <div class="span6"> |
|
152 |
- <input class="btn pull-right" type="submit" value=@Messages( "button.comment.new") /> |
|
153 |
- </div> |
|
154 |
- } |
|
118 |
+@form(routes.IssueApp.saveComment(project.owner, project.name, issue.id), |
|
119 |
+ 'id -> "comment-form", |
|
120 |
+ 'enctype -> "multipart/form-data"){ |
|
121 |
+ <div> |
|
122 |
+ <textarea id="contents" name="contents" class="span8 textbody .inputxx-large" rows="5" markdown="true"></textarea> |
|
123 |
+ <input class="btn add-on" type="submit" value=@Messages( "button.comment.new") /> |
|
124 |
+ </div> |
|
125 |
+ <div class="file-attachment"></div> |
|
126 |
+} |
|
127 |
+</div> |
|
128 |
+ |
|
129 |
+<div class="pull-right"> |
|
130 |
+ <a class="btn" href=""><i class="icon-ok"></i>@Messages("button.autoNotification")</a> |
|
131 |
+ <a class="btn" href="@routes.IssueApp.issues(project.owner, project.name,"open")">@Messages("button.list")</a> |
|
132 |
+ @isVisible(models.enumeration.Resource.ISSUE_POST) { |
|
133 |
+ <a class="btn btn-primary" href="@routes.IssueApp.editIssue(project.owner, project.name, issue.id)">@Messages("button.edit")</a> |
|
134 |
+ } |
|
155 | 135 |
</div> |
156 | 136 |
|
157 | 137 |
<!--삭제확인상자--> |
158 | 138 |
<div class="modal hide" id="deleteIssueConfirm"> |
159 |
- <div class="modal-header"> |
|
160 |
- <button type="button" class="close" data-dismiss="modal">×</button> |
|
161 |
- <h3>@Messages("issue.delete.window")</h3> |
|
162 |
- </div> |
|
163 |
- <div class="modal-body"> |
|
164 |
- <p>@Messages("post.delete.confirm")</p> |
|
165 |
- </div> |
|
166 |
- <div class="modal-footer"> |
|
167 |
- <a class="btn btn-danger" |
|
168 |
- href="@routes.IssueApp.deleteIssue(project.owner, project.name, issue.id)">@Messages("button.yes")</a> |
|
169 |
- <a href="#" class="btn" data-dismiss="modal">@Messages("button.no")</a> |
|
170 |
- </div> |
|
139 |
+ <div class="modal-header"> |
|
140 |
+ <button type="button" class="close" data-dismiss="modal">×</button> |
|
141 |
+ <h3>@Messages("issue.delete.window")</h3> |
|
142 |
+ </div> |
|
143 |
+ <div class="modal-body"> |
|
144 |
+ <p>@Messages("post.delete.confirm")</p> |
|
145 |
+ </div> |
|
146 |
+ <div class="modal-footer"> |
|
147 |
+ <a class="btn btn-danger" |
|
148 |
+ href="@routes.IssueApp.deleteIssue(project.owner, project.name, issue.id)">@Messages("button.yes")</a> |
|
149 |
+ <a href="#" class="btn" data-dismiss="modal">@Messages("button.no")</a> |
|
150 |
+ </div> |
|
171 | 151 |
</div> |
172 | 152 |
|
173 | 153 |
@views.html.markdown() |
174 | 154 |
<script type="text/javascript"> |
175 | 155 |
nforge.require('shortcut.submit'); |
176 |
- @for(label <- issue.labels) { |
|
177 |
- $('button.issue-label[labelId="@label.id"]').css('background-color', '@label.color'); |
|
178 |
- } |
|
179 | 156 |
nforge.require('nforge.issue.view'); |
180 |
- nforge.require('issue.label', '@routes.IssueLabelApp.getAll(project.owner, project.name)', '@routes.IssueLabelApp.post(project.owner, project.name)', {editable: false}); |
|
157 |
+ nforge.require('issue.label', '@routes.IssueLabelApp.getAll(project.owner, project.name)', '@routes.IssueLabelApp.post(project.owner, project.name)', {editable: false}, function() { |
|
158 |
+ @for(label <- issue.labels) { |
|
159 |
+ $('button.issue-label[labelId="@label.id"]').css('background-color', '@label.color'); |
|
160 |
+ $('button.issue-label[labelId="@label.id"]').css('color', contrasted_color('@label.color')); |
|
161 |
+ $('.labels button.btn[labelId="@label.id"]').addClass('active'); |
|
162 |
+ } |
|
163 |
+ }); |
|
181 | 164 |
</script> |
182 | 165 |
} |
--- app/views/issue/issueList.scala.html
+++ app/views/issue/issueList.scala.html
... | ... | @@ -64,23 +64,21 @@ |
64 | 64 |
<span selected>▼댓글순</span> |
65 | 65 |
</p> |
66 | 66 |
|
67 |
- <table> |
|
67 |
+ <table class="table issue-list"> |
|
68 | 68 |
<tbody> |
69 | 69 |
|
70 |
- @for(issue <- currentPage.getList.reverse){ |
|
70 |
+ @for(issue <- currentPage.getList){ |
|
71 | 71 |
<tr> |
72 | 72 |
<td class="no">@issue.id</td> |
73 | 73 |
<td class="attachmend attached">@if(Attachment.findByContainer(Resource.ISSUE_POST, issue.id).size > 0){<span class="icon-file"/>}</td> |
74 | 74 |
<td class="info"> |
75 | 75 |
<p><a href="@routes.IssueApp.issue(project.owner, project.name, issue.id)">@issue.title</a></p> |
76 |
- <p class="author">by <a href="/users/@issue.authorId">@issue.authorName</a> |
|
77 |
- </a> @agoString(issue.ago())</p> |
|
76 |
+ <p class="author">by <a href="/users/@issue.authorId">@Option(issue.getAuthorName).orElse(Option(Messages("issue.noAuthor"))).get</a>@agoString(issue.ago)</p> |
|
78 | 77 |
</td> |
79 | 78 |
<td class="state @issue.state.toString.toLowerCase">@Messages(issue.state.state)</td> |
80 | 79 |
<td class="comments">@issue.numOfComments</td> |
81 |
- <td class="assignee"><a href="#"><img class="user-picture" src="/assets/images/default-avatar-34.png"></a> |
|
82 |
- <span>@{ if (issue.assigneeId == null) Messages("issue.noAssignee") |
|
83 |
- else issue.assigneeName }</span> |
|
80 |
+ <td class="assignee"> |
|
81 |
+ <a href="#"><img class="user-picture" src="@urlToPicture(User.findById(issue.authorId).email, 34)"></a> |
|
84 | 82 |
</td> |
85 | 83 |
|
86 | 84 |
</tr> |
--- app/views/issue/newIssue.scala.html
+++ app/views/issue/newIssue.scala.html
... | ... | @@ -21,87 +21,55 @@ |
21 | 21 |
|
22 | 22 |
@form(action = routes.IssueApp.saveIssue(project.owner, project.name), |
23 | 23 |
'id -> "issue-form", |
24 |
- 'enctype -> "multipart/form-data", |
|
25 |
- 'class -> "form-horizontal", |
|
26 |
- 'onkeypress -> "return event.keyCode !== 13" |
|
24 |
+ 'enctype -> "multipart/form-data" |
|
27 | 25 |
) { |
28 | 26 |
<fieldset> |
29 | 27 |
@inputText( |
30 | 28 |
issueForm("title"), |
31 | 29 |
'_showConstraints -> false, |
32 |
- '_label-> Messages("post.new.title"), |
|
33 |
- 'class -> "input-xxlarge") |
|
30 |
+ '_label -> null, |
|
31 |
+ 'class -> "title", |
|
32 |
+ 'placeholder -> Messages("post.new.title"), |
|
33 |
+ 'onkeypress -> "return event.keyCode !== 13" |
|
34 |
+ ) |
|
34 | 35 |
@textarea( |
35 | 36 |
issueForm("body"), |
36 | 37 |
'_showConstraints -> false, |
37 |
- '_label-> Messages("post.new.contents"), |
|
38 |
+ '_label -> null, |
|
38 | 39 |
'rows -> 16, |
39 |
- 'class -> "input-xxlarge textbody", |
|
40 |
+ 'class -> "textbody", |
|
40 | 41 |
'markdown -> true) |
41 | 42 |
</fieldset> |
42 | 43 |
|
43 |
- <!-- issue.label module make this as label selector --> |
|
44 |
- <fieldset class="labels"></fieldset> |
|
45 |
- |
|
46 |
- <fieldset> |
|
47 |
- <div class="well"> |
|
48 |
- <legend><b>@Messages("issue.new.environment")</b></legend> |
|
49 |
- <div class="well form-inline"> |
|
50 |
- @selectEx( |
|
51 |
- issueForm("osType"), |
|
52 |
- options(Issue.osTypes), |
|
53 |
- '_label-> Messages("issue.new.environment.osType"), |
|
54 |
- '_default -> Messages("issue.new.selectDefault.osType"), |
|
55 |
- '_showConstraints -> false) |
|
56 |
- |
|
57 |
- @selectEx( |
|
58 |
- issueForm("browserType"), |
|
59 |
- options(Issue.browserTypes), |
|
60 |
- '_label-> Messages("issue.new.environment.browserType"), |
|
61 |
- '_default -> Messages("issue.new.selectDefault.browserType"), |
|
62 |
- '_showConstraints -> false) |
|
63 |
- @selectEx( |
|
64 |
- issueForm("dbmsType"), |
|
65 |
- options(Issue.dbmsTypes), |
|
66 |
- '_label-> Messages("issue.new.environment.dbmsType"), |
|
67 |
- '_default -> Messages("issue.new.selectDefault.dbmsType"), |
|
68 |
- '_showConstraints -> false) |
|
69 |
- </div> |
|
70 |
- </div> |
|
44 |
+ <!-- issue.label js module appends a label selector here. --> |
|
45 |
+ <fieldset class="labels"> |
|
46 |
+ @isVisible(models.enumeration.Resource.ISSUE_ASSIGNEE) { |
|
47 |
+ @select( |
|
48 |
+ issueForm("assigneeId"), |
|
49 |
+ options(ProjectUser.options(project.id)), |
|
50 |
+ '_label-> Messages("issue.new.detailInfo.assignee"), |
|
51 |
+ '_default -> Messages("issue.new.selectDefault.assignee"), |
|
52 |
+ '_showConstraints -> false) |
|
53 |
+ } |
|
54 |
+ @isVisible(models.enumeration.Resource.ISSUE_MILESTONE) { |
|
55 |
+ @select( |
|
56 |
+ issueForm("milestoneId"), |
|
57 |
+ options(Milestone.options(project.id)), |
|
58 |
+ '_label-> Messages("issue.new.detailInfo.milestone"), |
|
59 |
+ '_default -> Messages("issue.new.selectDefault.milestone"), |
|
60 |
+ '_showConstraints -> false) |
|
61 |
+ } |
|
71 | 62 |
</fieldset> |
72 | 63 |
|
73 |
- <fieldset> |
|
74 |
- <div class="well"> |
|
75 |
- <legend><b>@Messages("issue.new.detailInfo")</b></legend> |
|
76 |
- <div class="well form-inline"> |
|
77 |
- @isVisible(models.enumeration.Resource.ISSUE_ASSIGNEE) { |
|
78 |
- @select( |
|
79 |
- issueForm("assigneeId"), |
|
80 |
- options(ProjectUser.options(project.id)), |
|
81 |
- '_label-> Messages("issue.new.detailInfo.assignee"), |
|
82 |
- '_default -> Messages("issue.new.selectDefault.assignee"), |
|
83 |
- '_showConstraints -> false) |
|
84 |
- } |
|
85 |
- @isVisible(models.enumeration.Resource.ISSUE_MILESTONE) { |
|
86 |
- @select( |
|
87 |
- issueForm("milestoneId"), |
|
88 |
- options(Milestone.options(project.id)), |
|
89 |
- '_label-> Messages("issue.new.detailInfo.milestone"), |
|
90 |
- '_default -> Messages("issue.new.selectDefault.milestone"), |
|
91 |
- '_showConstraints -> false) |
|
92 |
- } |
|
93 |
- </div> |
|
94 |
- </div> |
|
95 |
- </fieldset> |
|
96 | 64 |
|
97 | 65 |
<div class="row pull-right"> |
98 |
- <div class="actions"> |
|
99 |
- <input type="submit" class="btn btn-primary" value="@Messages("button.save")"> |
|
100 |
- <a href="@routes.IssueApp.issues(project.owner, project.name, "all")" class="btn">@Messages("button.cancel")</a> |
|
101 |
- </div> |
|
66 |
+ <div class="actions"> |
|
67 |
+ <input type="submit" class="btn btn-primary" value="@Messages("button.save")"> |
|
68 |
+ <a href="@routes.IssueApp.issues(project.owner, project.name, "all")" class="btn">@Messages("button.cancel")</a> |
|
69 |
+ </div> |
|
102 | 70 |
</div> |
103 | 71 |
} |
104 |
- |
|
72 |
+@board.postVaildate() |
|
105 | 73 |
@views.html.markdown() |
106 | 74 |
<script type="text/javascript"> |
107 | 75 |
nforge.require('shortcut.submit'); |
--- app/views/twitterBootstrapInput.scala.html
+++ app/views/twitterBootstrapInput.scala.html
... | ... | @@ -4,9 +4,11 @@ |
4 | 4 |
* Generate input according twitter bootsrap rules * |
5 | 5 |
**************************************************@ |
6 | 6 |
<div class="control-group @if(elements.hasErrors) {error}"> |
7 |
+ @if(elements.label != null) { |
|
7 | 8 |
<label class="control-label" for="@elements.id">@capitalize(elements.label.toString()) |
8 | 9 |
<em class="help-inline">@displayRequiredStar(elements.infos.mkString(", "))</em> |
9 | 10 |
</label> |
11 |
+ } |
|
10 | 12 |
<div class="controls"> |
11 | 13 |
@elements.input |
12 | 14 |
<em class="help-inline">@removeRequiredMessage(elements.infos.mkString(", "))</em> |
--- conf/initial-data.yml
+++ conf/initial-data.yml
... | ... | @@ -100,6 +100,7 @@ |
100 | 100 |
body: 내용 불필요한~ |
101 | 101 |
state: OPEN |
102 | 102 |
milestoneId: 1 |
103 |
+ date: 2012-11-01 08:00:00 |
|
103 | 104 |
project: !!models.Project |
104 | 105 |
id: 1 |
105 | 106 |
- !!models.Issue |
... | ... | @@ -109,6 +110,7 @@ |
109 | 110 |
body: 내용 다운로드는 익명 댓글에도 사용자명에 링크가 걸림 |
110 | 111 |
state: OPEN |
111 | 112 |
milestoneId: 2 |
113 |
+ date: 2012-11-02 08:00:00 |
|
112 | 114 |
project: !!models.Project |
113 | 115 |
id: 1 |
114 | 116 |
- !!models.Issue |
... | ... | @@ -118,6 +120,7 @@ |
118 | 120 |
body: 내용 gittracker.php의 메모리 제한 에러 |
119 | 121 |
state: CLOSED |
120 | 122 |
milestoneId: 2 |
123 |
+ date: 2012-11-03 08:00:00 |
|
121 | 124 |
project: !!models.Project |
122 | 125 |
id: 1 |
123 | 126 |
numOfComments: 1 |
... | ... | @@ -128,6 +131,7 @@ |
128 | 131 |
body: 내용 git/hg 코드 브라우저에 i18n이 적용되지 않음 |
129 | 132 |
state: CLOSED |
130 | 133 |
milestoneId: 2 |
134 |
+ date: 2012-11-04 08:00:00 |
|
131 | 135 |
project: !!models.Project |
132 | 136 |
id: 1 |
133 | 137 |
- !!models.Issue |
... | ... | @@ -137,6 +141,7 @@ |
137 | 141 |
body: IOS는 설치 못하나요? |
138 | 142 |
state: OPEN |
139 | 143 |
milestoneId: 0 |
144 |
+ date: 2012-11-05 08:00:00 |
|
140 | 145 |
project: !!models.Project |
141 | 146 |
id: 3 |
142 | 147 |
- !!models.Issue |
... | ... | @@ -146,6 +151,7 @@ |
146 | 151 |
body: 메모리가 너무 누수가 되는듯. |
147 | 152 |
state: CLOSED |
148 | 153 |
milestoneId: 6 |
154 |
+ date: 2012-11-06 08:00:00 |
|
149 | 155 |
project: !!models.Project |
150 | 156 |
id: 3 |
151 | 157 |
- !!models.Issue |
... | ... | @@ -155,6 +161,7 @@ |
155 | 161 |
body: Please make it. |
156 | 162 |
state: OPEN |
157 | 163 |
milestoneId: 5 |
164 |
+ date: 2012-11-07 08:00:00 |
|
158 | 165 |
project: !!models.Project |
159 | 166 |
id: 3 |
160 | 167 |
- !!models.Issue |
... | ... | @@ -164,6 +171,7 @@ |
164 | 171 |
body: CPU를 무한사용 중이에요. |
165 | 172 |
state: CLOSED |
166 | 173 |
milestoneId: 5 |
174 |
+ date: 2012-11-08 08:00:00 |
|
167 | 175 |
project: !!models.Project |
168 | 176 |
id: 3 |
169 | 177 |
- !!models.Issue |
... | ... | @@ -173,6 +181,7 @@ |
173 | 181 |
body: When using Play, when I chain less files such as a.less imports b.less which in turn imports c.less |
174 | 182 |
state: CLOSED |
175 | 183 |
milestoneId: 1 |
184 |
+ date: 2012-11-09 08:00:00 |
|
176 | 185 |
project: !!models.Project |
177 | 186 |
id: 1 |
178 | 187 |
- !!models.Issue |
... | ... | @@ -182,6 +191,7 @@ |
182 | 191 |
body: The following code works as expected in RC1-Snapshot, but breaks with an TypeDoesNotMatch exception in RC-3 and 2.0 final. |
183 | 192 |
state: CLOSED |
184 | 193 |
milestoneId: 1 |
194 |
+ date: 2012-11-10 08:00:00 |
|
185 | 195 |
project: !!models.Project |
186 | 196 |
id: 1 |
187 | 197 |
- !!models.Issue |
... | ... | @@ -190,6 +200,7 @@ |
190 | 200 |
body: PostgreSQL allows you to insert a row and obtain the id of the new row in one query |
191 | 201 |
state: OPEN |
192 | 202 |
milestoneId: 1 |
203 |
+ date: 2012-11-11 08:00:00 |
|
193 | 204 |
project: !!models.Project |
194 | 205 |
id: 1 |
195 | 206 |
- !!models.Issue |
... | ... | @@ -199,6 +210,7 @@ |
199 | 210 |
body: Create sbt project definition module, if exists, for each subproject (pull 128) |
200 | 211 |
state: CLOSED |
201 | 212 |
milestoneId: 3 |
213 |
+ date: 2012-11-12 08:00:00 |
|
202 | 214 |
project: !!models.Project |
203 | 215 |
id: 2 |
204 | 216 |
- !!models.Issue |
... | ... | @@ -208,6 +220,7 @@ |
208 | 220 |
body: While creating some complex forms, Eclipse started hanging for some unknown reason. |
209 | 221 |
state: CLOSED |
210 | 222 |
milestoneId: 3 |
223 |
+ date: 2012-11-13 08:00:00 |
|
211 | 224 |
project: !!models.Project |
212 | 225 |
id: 2 |
213 | 226 |
- !!models.Issue |
... | ... | @@ -217,6 +230,7 @@ |
217 | 230 |
body: While creating some complex forms, Eclipse started hanging for some unknown reason. |
218 | 231 |
state: OPEN |
219 | 232 |
milestoneId: 4 |
233 |
+ date: 2012-11-14 08:00:00 |
|
220 | 234 |
project: !!models.Project |
221 | 235 |
id: 2 |
222 | 236 |
- !!models.Issue |
... | ... | @@ -226,6 +240,7 @@ |
226 | 240 |
body: With Play 2.0.2, when creating a cookie with response().setCookie, HTTP output is like |
227 | 241 |
state: OPEN |
228 | 242 |
milestoneId: 4 |
243 |
+ date: 2012-11-15 08:00:00 |
|
229 | 244 |
project: !!models.Project |
230 | 245 |
id: 2 |
231 | 246 |
- !!models.Issue |
... | ... | @@ -235,6 +250,7 @@ |
235 | 250 |
body: Test on Play2.1-Snapshot |
236 | 251 |
state: OPEN |
237 | 252 |
milestoneId: 4 |
253 |
+ date: 2012-11-15 08:00:00 |
|
238 | 254 |
project: !!models.Project |
239 | 255 |
id: 2 |
240 | 256 |
- !!models.Issue |
... | ... | @@ -244,6 +260,7 @@ |
244 | 260 |
body: Test on Play2.1-Snapshot |
245 | 261 |
state: OPEN |
246 | 262 |
milestoneId: 4 |
263 |
+ date: 2012-11-16 08:00:00 |
|
247 | 264 |
project: !!models.Project |
248 | 265 |
id: 3 |
249 | 266 |
issueComments: |
--- conf/messages.ko
+++ conf/messages.ko
... | ... | @@ -126,7 +126,7 @@ |
126 | 126 |
issue.new.result.diagnosisResult.unreproducible = 재현불가 |
127 | 127 |
issue.new.result.diagnosisResult.duplicated = 중복 |
128 | 128 |
issue.new.result.diagnosisResult.works4me= 잘동작함 |
129 |
-issue.noAssignee = 미배정 |
|
129 |
+issue.noAssignee = 담당자 없음 |
|
130 | 130 |
issue.delete.window = 이슈 삭제 |
131 | 131 |
issue.comment.delete.window = 이슈 댓글 삭제 |
132 | 132 |
issue.new.selectDefault.milestone = --마일스톤 선택-- |
--- public/javascripts/modules/issue.js
+++ public/javascripts/modules/issue.js
... | ... | @@ -13,7 +13,7 @@ |
13 | 13 |
var that; |
14 | 14 |
|
15 | 15 |
that = { |
16 |
- init: function(urlToLabels, urlToPost, options) { |
|
16 |
+ init: function(urlToLabels, urlToPost, options, callback) { |
|
17 | 17 |
that.urlToLabels = urlToLabels; |
18 | 18 |
that.urlToPost = urlToPost; |
19 | 19 |
that.options = { |
... | ... | @@ -27,12 +27,13 @@ |
27 | 27 |
that.addLabelEditor(); |
28 | 28 |
} |
29 | 29 |
that.setEvent(); |
30 |
- that.updateLabels(); |
|
30 |
+ that.updateLabels(callback); |
|
31 | 31 |
}, |
32 | 32 |
|
33 | 33 |
addLabelEditor: function() { |
34 | 34 |
var div = $('<div>') |
35 |
- .addClass('control-group'); |
|
35 |
+ .addClass('control-group') |
|
36 |
+ .addClass('label-editor'); |
|
36 | 37 |
|
37 | 38 |
var label = $('<label id="custom-label-label">') |
38 | 39 |
.addClass('control-label') |
... | ... | @@ -57,7 +58,7 @@ |
57 | 58 |
} |
58 | 59 |
|
59 | 60 |
var input_color = $('<input id="custom-label-color" type="text">') |
60 |
- .addClass('input-medium') |
|
61 |
+ .addClass('input-small') |
|
61 | 62 |
.attr('placeholder', 'Custom Color'); |
62 | 63 |
|
63 | 64 |
var input_category = $('<input id="custom-label-category" type="text">') |
... | ... | @@ -83,7 +84,7 @@ |
83 | 84 |
$('fieldset.labels').append(div); |
84 | 85 |
}, |
85 | 86 |
|
86 |
- updateLabels: function() { |
|
87 |
+ updateLabels: function(callback) { |
|
87 | 88 |
var form = $('<form>') |
88 | 89 |
.attr('method', 'get') |
89 | 90 |
.attr('action', that.urlToLabels); |
... | ... | @@ -100,33 +101,56 @@ |
100 | 101 |
for (var i = 0; i < labels.length; i++) { |
101 | 102 |
that.add_label_into_category(labels[i].id, labels[i].category, labels[i].name, labels[i].color); |
102 | 103 |
} |
104 |
+ |
|
105 |
+ if (callback) callback(); |
|
103 | 106 |
} |
104 | 107 |
}); |
105 | 108 |
|
106 | 109 |
form.submit(); |
107 | 110 |
}, |
108 | 111 |
|
112 |
+ updateSelectedColor: function(color) { |
|
113 |
+ // Change the name input area's color to the selected color. |
|
114 |
+ contrasted = contrasted_color(color); |
|
115 |
+ $('#custom-label-name').css('background-color', color); |
|
116 |
+ $('#custom-label-name').css('color', contrasted); |
|
117 |
+ selectors = ['#custom-label-name:-moz-placeholder', |
|
118 |
+ '#custom-label-name:-ms-input-placeholder', |
|
119 |
+ '#custom-label-name::-webkit-input-placeholder']; |
|
120 |
+ for (var i in selectors) { |
|
121 |
+ try { |
|
122 |
+ document.styleSheets[0].addRule(selectors[i], 'color: ' + contrasted + ' !important'); |
|
123 |
+ document.styleSheets[0].addRule(selectors[i], 'opacity: 0.8'); |
|
124 |
+ } catch (e) { |
|
125 |
+ continue; |
|
126 |
+ } |
|
127 |
+ } |
|
128 |
+ }, |
|
129 |
+ |
|
109 | 130 |
setEvent: function() { |
110 | 131 |
$('#custom-label button.issue-label').click(function(e) { |
132 |
+ var color, contrasted, selectors; |
|
133 |
+ |
|
134 |
+ // Set clicked button active. |
|
111 | 135 |
$('#custom-label button.issue-label').removeClass('active'); |
112 | 136 |
$(e.srcElement).addClass('active'); |
113 |
- $('#custom-label-color').val($(e.srcElement).css('background-color')); |
|
137 |
+ |
|
138 |
+ // Get the selected color. |
|
139 |
+ color = $(e.srcElement).css('background-color'); |
|
140 |
+ |
|
141 |
+ // Fill the color input area with the hexadecimal value of |
|
142 |
+ // the selected color. |
|
143 |
+ $('#custom-label-color').val(new RGBColor(color).toHex()); |
|
144 |
+ |
|
145 |
+ that.updateSelectedColor(color); |
|
146 |
+ |
|
147 |
+ // Focus to the category input area. |
|
114 | 148 |
$('#custom-label-category').focus(); |
115 |
- $('#custom-label-name').css('background-color', $(e.srcElement).css('background-color')); |
|
116 |
- var contrasted = contrasted_color($(e.srcElement).css('background-color')); |
|
117 |
- $('#custom-label-name').css('color', contrasted); |
|
118 |
- try { |
|
119 |
- document.styleSheets[0].addRule('#custom-label-name:-moz-placeholder', 'color: ' + contrasted); |
|
120 |
- document.styleSheets[0].addRule('#custom-label-name:-ms-input-placeholder', 'color: ' + contrasted); |
|
121 |
- } catch (e) { |
|
122 |
- document.styleSheets[0].addRule('#custom-label-name::-webkit-input-placeholder', 'color: ' + contrasted); |
|
123 |
- } |
|
124 | 149 |
}); |
125 | 150 |
|
126 | 151 |
var issueForm = $('form#issue-form'); |
127 | 152 |
issueForm.submit(function() { |
128 | 153 |
var buttons = $('fieldset.labels div[category] button.active'); |
129 |
- |
|
130 | 154 |
for (var i = 0; i < buttons.length; i++) { |
131 | 155 |
issueForm.append( |
132 | 156 |
'<input type="hidden" name="labelIds[]" value="' + $(buttons[i]).attr('labelId') + '">'); |
... | ... | @@ -142,6 +166,13 @@ |
142 | 166 |
$('#custom-label input').keyup(function(e) { |
143 | 167 |
if (e.keyCode == 13) { |
144 | 168 |
that.add_custom_label(); |
169 |
+ } |
|
170 |
+ }); |
|
171 |
+ |
|
172 |
+ $('#custom-label-color').keyup(function(e) { |
|
173 |
+ var color = $(e.srcElement).val(); |
|
174 |
+ if (new RGBColor(color).ok) { |
|
175 |
+ that.updateSelectedColor(color); |
|
145 | 176 |
} |
146 | 177 |
}); |
147 | 178 |
}, |
... | ... | @@ -201,7 +232,13 @@ |
201 | 232 |
var controls = $('<div class="controls" data-toggle="buttons-checkbox" category="' + category + '">'); |
202 | 233 |
div.append(label); |
203 | 234 |
div.append(controls); |
204 |
- $('fieldset.labels').prepend(div); |
|
235 |
+ labelEditor = $('.label-editor'); |
|
236 |
+ console.log(labelEditor); |
|
237 |
+ if (labelEditor.length > 0) { |
|
238 |
+ $('.label-editor').before(div); |
|
239 |
+ } else { |
|
240 |
+ $('.labels').append(div); |
|
241 |
+ } |
|
205 | 242 |
|
206 | 243 |
// Edit Button |
207 | 244 |
/* |
--- public/stylesheets/issue.css
+++ public/stylesheets/issue.css
... | ... | @@ -1,26 +1,9 @@ |
1 | 1 |
.gnb-outer .gnb-inner .gnb-menu-wrap .gnb-menus .menu.active { color: #5dd4ff } |
2 | 2 |
|
3 |
-body { font-size: 12px; } |
|
4 |
-.page-padding { padding-left: 40px; padding-right: 40px; padding-top: 30px; } |
|
5 |
-table { font-size: 12px; color: #333333; width: 100%; } |
|
6 |
-td:first-child { padding-left: 20px; } |
|
7 |
-td:last-child { padding-right: 20px; } |
|
8 |
-td.no { font-size: 11px; color: #b2b2b2; } |
|
9 |
-td.comments { font-size: 12px; color: #4489a4; } |
|
10 |
-td.state { font-weight: bold; } |
|
11 |
-td.state.opened, td.state.enrolled, td.state.assigned { color: #51AACC; } |
|
12 |
-td.state.closed { color: #bbbbbb; } |
|
13 |
- |
|
14 |
-tbody td { border-bottom: 1px solid #cdcdcd; margin: 0px; padding-top: 15px; padding-bottom: 5px; } |
|
15 |
-.info .author { color: #999999; } |
|
16 |
-.info .author a { color: #696969; } |
|
17 |
-td.info p { margin: 0px; } |
|
3 |
+/* issue common */ |
|
4 |
+.page-padding { padding-top: 30px; } |
|
18 | 5 |
|
19 | 6 |
.user-picture { border: 1px solid #cccccc; border-radius: 4px; } |
20 |
- |
|
21 |
-p.ordering { text-align: right; } |
|
22 |
-p.ordering span { color: #949494 } |
|
23 |
-p.ordering span[selected] { color: #666666 } |
|
24 | 7 |
|
25 | 8 |
.breadcrumb { background: none; } |
26 | 9 |
.breadcrumb .divider { color: #adadad } |
... | ... | @@ -29,20 +12,29 @@ |
29 | 12 |
.breadcrumb.project-name { color: #4092A1; font-size: 26px; font-family: Din; font-weight: normal; padding: 0px; letter-spacing: -1px; } |
30 | 13 |
.breadcrumb.project-name .active { color: #4092A1; font-weight: lighter; } |
31 | 14 |
|
32 |
-/* |
|
33 |
-.dashboard { background-color: #ececec; padding: 10px; border-radius: 6px; } |
|
34 |
-.dashboard div { margin: 0px; } |
|
35 |
-.dashboard div div.emboss { display: inline; background-color: #ffffff; border-radius: 6px; border-bottom: 1px solid #c5c5c5; padding: 10px 15px 15px 10px; vertical-align: top; } |
|
36 |
-.dashboard div.emboss div { display: inline; padding: 0px; vertical-align: top; } |
|
37 |
-.dashboard div { font-weight: normal;} |
|
38 |
-.dashboard div span { font-weight: bold; } |
|
39 |
-.dashboard div.number { font-family: DIN; font-size: 30px; font-weight: bold; letter-spacing: -1px; text-shadow: #c9c9c9 0px -1px 0px; } |
|
40 |
-.dashboard div.all { color: #666666 } |
|
41 |
-.dashboard div.all { color: #f28149 } |
|
42 |
-.dashboard div.open { color: #666666 } |
|
43 |
-.dashboard div.open { color: #51aacc } |
|
44 |
-*/ |
|
15 |
+.info .author { color: #999999; } |
|
16 |
+.info .author a { color: #696969; } |
|
17 |
+.info p { margin: 0px; } |
|
45 | 18 |
|
19 |
+/* issue list */ |
|
20 |
+body { font-size: 12px; } |
|
21 |
+.issue-list { color: #333333; } |
|
22 |
+.issue-list td { border-color: #cdcdcd; } |
|
23 |
+.issue-list tr:first-child td { border-top: none; } |
|
24 |
+.issue-list tr:last-child td { border-bottom: 1px solid #cdcdcd; } |
|
25 |
+.issue-list td.no { font-size: 11px; color: #b2b2b2; } |
|
26 |
+.issue-list td.info { width: 100%; } |
|
27 |
+.issue-list td.state { font-weight: bold; } |
|
28 |
+.issue-list td.state.opened { color: #51AACC; } |
|
29 |
+.issue-list td.state.closed { color: #bbbbbb; } |
|
30 |
+.issue-list td.comments { color: #4489a4; } |
|
31 |
+.issue-list td.assignee { min-width: 34px; } |
|
32 |
+ |
|
33 |
+p.ordering { text-align: right; } |
|
34 |
+p.ordering span { color: #949494 } |
|
35 |
+p.ordering span[selected] { color: #666666 } |
|
36 |
+ |
|
37 |
+/* dashboard */ |
|
46 | 38 |
.dashboard { background-color: #ececec; padding: 10px; border-radius: 6px; } |
47 | 39 |
.dashboard dl { margin: 0px; } |
48 | 40 |
.dashboard dl div { display: inline-block; background-color: #ffffff; border-radius: 6px; box-shadow: 0px 1px 0px #c5c5c5; padding: 10px 15px 15px 10px; } |
... | ... | @@ -56,8 +48,6 @@ |
56 | 48 |
.dashboard dt.open { color: #666666 } |
57 | 49 |
.dashboard dd.open { color: #51aacc } |
58 | 50 |
|
59 |
-/* .dashboard dd { font-family: DIN; font-size: 30px; font-weight: bold; letter-spacing: -1px; color: rgba(255, 255, 255, 0.2); text-shadow: #d06e3f 0px -1px 0px; } */ |
|
60 |
- |
|
61 | 51 |
.dashboard hr { border-top-color: #d4d4d4; border-bottom-color: #ffffff; margin: 10px 0px 10px 0px; } |
62 | 52 |
|
63 | 53 |
.dashboard form { background-color: #fff; padding: 15px 10px 15px 10px; border-radius: 6px; border-bottom: 1px solid #c5c5c5; margin: 0px; } |
... | ... | @@ -66,11 +56,7 @@ |
66 | 56 |
.dashboard .btn-flat { background-color: #fff; border: 1px solid; border-color: #e1e1e1 #c4c4c4 #c4c4c4 #e1e1e1; color: #707070; background: none; } |
67 | 57 |
.dashboard .btn-small { font-weight: bold; } |
68 | 58 |
|
69 |
-.active.active-dimgray { color: dimgray } |
|
70 |
-.active.active-white { color: white } |
|
71 |
-.active .active-dimgray { color: dimgray } |
|
72 |
-.active .active-white { color: white } |
|
73 |
- |
|
59 |
+/* label */ |
|
74 | 60 |
.labels { background-color: #f6f6f6; } |
75 | 61 |
.labels { border-top: 1px solid #e2e2e2; } |
76 | 62 |
.labels .controls button.btn { margin-left: 5px; } |
... | ... | @@ -95,11 +81,6 @@ |
95 | 81 |
} |
96 | 82 |
|
97 | 83 |
.labels input { margin-left: 7px; } |
98 |
-/* |
|
99 |
-#custom-label-color:after { content: "|" } |
|
100 |
-#custom-label-category:before { content: "|" } |
|
101 |
-#custom-label-name:before { content: "|" } |
|
102 |
-*/ |
|
103 | 84 |
|
104 | 85 |
.labels a.del-link { |
105 | 86 |
border: none; |
... | ... | @@ -110,4 +91,58 @@ |
110 | 91 |
} |
111 | 92 |
.labels a.del-link:hover { opacity: 0.7; } |
112 | 93 |
|
113 |
-/* .del-link { display: none; } */ |
|
94 |
+.active.active-dimgray { color: dimgray } |
|
95 |
+.active.active-white { color: white } |
|
96 |
+.active .active-dimgray { color: dimgray } |
|
97 |
+.active .active-white { color: white } |
|
98 |
+ |
|
99 |
+/* issue */ |
|
100 |
+.number { color: #51aacc; } |
|
101 |
+ |
|
102 |
+article hgroup { padding: 14px 0px 13px 0px; border-color: rgb(224, 224, 224); border-style: solid; border-width: 1px 0px 1px 0px; } |
|
103 |
+article .issue-info-group { border-top: 2px solid rgb(251, 250, 248); padding-top: 20px; } |
|
104 |
+article .issue-info-group .assignee { color: #333333; font-size: 11px; margin-bottom: 0px; } |
|
105 |
+article .issue-info-group .no-assignee { color: rgb(173, 173, 173); } |
|
106 |
+article .issue-info-group .assignee-name { color: #5c5c5c; font-size: 12px; font-weight: bold; margin-bottom: 0px; } |
|
107 |
+article .issue-info { float: left; } |
|
108 |
+article .issue-etc-info { float: none; color: #646464; font-size: 11px; } |
|
109 |
+article .title { color: #000000; font-size: 14px; font-weight: bold; } |
|
110 |
+article .body { color: #666666; margin-top: 22px; } /* 34px = 22px + 9px of <p> + (line height 18px - font size 12px) / 2 */ |
|
111 |
+ |
|
112 |
+/* comment */ |
|
113 |
+.comment-area { |
|
114 |
+ margin-top: 40px; |
|
115 |
+ border-radius: 6px; |
|
116 |
+ background-color: #f5f5f5; |
|
117 |
+ color: #666666; |
|
118 |
+} |
|
119 |
+.comment-area .info { |
|
120 |
+ color: #222222; |
|
121 |
+ font-weight: bold; |
|
122 |
+ font-size: 14px; |
|
123 |
+ border-bottom: 1px solid #e6e6e6; |
|
124 |
+ margin-bottom: 0px; |
|
125 |
+ padding-left: 20px; |
|
126 |
+ padding-top: 11px; |
|
127 |
+ padding-bottom: 11px; |
|
128 |
+} |
|
129 |
+.comment-area .comments { border-top: 1px solid #ffffff; border-bottom: 1px solid #e6e6e6; } |
|
130 |
+.comment-area .comments { padding-left: 20px; padding-right: 20px; } /* 40px = 31px + 9px of <p> */ |
|
131 |
+.comment-area .comment { |
|
132 |
+ border-bottom: 1px solid #e6e6e6; |
|
133 |
+ padding-top: 15px; |
|
134 |
+ padding-bottom: 6px; |
|
135 |
+}/* 15 = 6px + 9px of <p> */ |
|
136 |
+.comment-area .comment .author { color: #646464; font-weight: bold; } |
|
137 |
+.comment-area .comment .date { color: #bdbdbd; } |
|
138 |
+.comment-area .comment:last-child { border-bottom: none; } |
|
139 |
+.comment-area #comment-form { padding-left: 20px; padding-top: 17px; border-top: 1px solid #ffffff; } |
|
140 |
+.comment-area .comment .span11 { margin-left: 0px; } |
|
141 |
+ |
|
142 |
+#issue-form input.title { width: 100%; } |
|
143 |
+#issue-form textarea.textbody { width: 100%; } |
|
144 |
+ |
|
145 |
+#issue-form .file-attachment { padding: 20px; padding-top: 15px; border-radius: 6px; background-color: #f5f5f5; } |
|
146 |
+#issue-form .attachment-list { border-radius: 4px; background-color: #dfdfdf; padding: 20px; } |
|
147 |
+#issue-form .attachment-list li { list-style-type: none; } |
|
148 |
+#issue-form .file-attachment .close { margin-left: 10px; float: none; } |
Add a comment
Delete comment
Once you delete this comment, you won't be able to recover it. Are you sure you want to delete this comment?