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