Jihan Kim 2014-07-15
Issue: improve layout
- Disable label form and show "Manage Label" text
  on search form at issue list if no label exists.
- issue.View.js has rewritted with new coding convention.
- Separate pjax-filter links into partial template file.
- Set issue-info wrap affixed
@60ebf00981d83b286f5c4ab422b2f53e7491f8b8
app/assets/stylesheets/less/_override.less
--- app/assets/stylesheets/less/_override.less
+++ app/assets/stylesheets/less/_override.less
@@ -10,6 +10,7 @@
 .select2-container {
     .avatar-wrap { margin-top:0; }
 
+    text-align:left;
     background-color:#fff;
     border:1px solid rgba(0,0,0,0.15);
 
@@ -57,6 +58,10 @@
                 margin-left: 0;
             }
         }
+
+        abbr {
+            top:7px;
+        }
     }
 
     &.select2-drop-above {
@@ -95,7 +100,7 @@
 }
 
 .select2-results {
-    max-height: 400px;
+    max-height: 350px;
     padding:4px; margin:4px 0 0 0;
 
     li { margin-bottom:1px; }
@@ -212,12 +217,22 @@
 
         .select2-search-field input {
             font-family:@base-font-family !important;
+            padding:3px 5px;
         }
     }
 
     &.issue-labels {
         border:none;
         .box-shadow(none);
+
+        &.bordered {
+            border:1px solid #ddd;
+
+            &:hover,
+            &.select2-container-active {
+                border:1px solid #ddd;
+            }
+        }
 
         .select2-choices {
             .select2-search-choice {
@@ -264,6 +279,12 @@
             .box-shadow(none);
         }
     }
+
+    &.select2-container-disabled {
+        .select2-choices {
+            border:none;
+        }
+    }
 }
 .select2-drop.issue-labels {
     min-width:240px;
@@ -273,6 +294,15 @@
 
     .select2-results {
         min-width: 220px;
+    }
+    .select2-result-with-children > .select2-result-label {
+        padding: 10px 7px;
+    }
+    .select2-result-with-children > .select2-result-label:first-of-type {
+        padding-top: 0;
+    }
+    .select2-result-with-children > .select2-result-sub > li:last-of-type {
+        margin-bottom: 10px;
     }
 }
 li.select2-result-with-children:first-of-type {
@@ -288,12 +318,16 @@
     display: none;
 }
 .select2-drop.branches {
-    width:auto !important;
-    border-top:1px solid rgba(0,0,0,0.15);
-    .box-shadow(2px 2px 0 rgba(0,0,0,0.05));
+    width: auto !important;
+    border-top: 1px solid rgba(0, 0, 0, 0.15);
+    .box-shadow(2px 2px 0 rgba(0, 0, 0, 0.05));
     .border-radius(3px);
 
     .select2-results {
         width: auto;
     }
 }
+.select2-result-label .issue-label {
+    display: block;
+    margin-right: 15px;
+}
app/assets/stylesheets/less/_page.less
--- app/assets/stylesheets/less/_page.less
+++ app/assets/stylesheets/less/_page.less
@@ -2595,41 +2595,26 @@
 
 .board-body {
     .author-info {
-        padding:15px 0 0 20px;
-
-        dl { margin-bottom:20px; }
-        dl.author {
-            height:51px; overflow:hidden;
-            dd { padding:5px 10px; }
-        }
-        p {
-            margin-bottom: 5px;
-            line-height: 14px;
-        }
-        .name {
-            font-size: 11px;
-        }
-        .status {
-            margin: 0;
-            color: #646464;
-            font-size: 11px;
-            .ico {
-                vertical-align: top;
-            }
-        }
+        display:block;
+        margin:10px 20px;
     }
+
     .content {
-       padding:0 20px;
-       margin-bottom: 20px;
-       a { font-weight:bold; }
+        padding:0 20px;
+        margin-bottom: 20px;
+        min-height: 250px;
+
+        a { font-weight:bold; }
 
         &.empty-content {
             text-align: center;
-            min-height: 250px;
             background-image:url("@{base-image-path}/no_contents.jpg");
             background-repeat: no-repeat;
             background-position: center 50%;
-       }
+        }
+    }
+    textarea.content {
+        padding:6px;
     }
 }
 .attachments {
@@ -3040,6 +3025,37 @@
     position:relative;
 
     select { visibility: hidden; }
+}
+
+.issue-info {
+    padding:15px 0 0 52px;
+    &.affix { top: 0; }
+
+    dl {
+        margin-bottom:20px;
+
+        dd {
+            padding:5px 0px;
+        }
+    }
+
+    p {
+        margin-bottom: 5px;
+        line-height: 14px;
+    }
+
+    .name {
+        font-size: 11px;
+    }
+
+    .status {
+        margin: 0;
+        color: #646464;
+        font-size: 11px;
+        .ico {
+            vertical-align: top;
+        }
+    }
 }
 
 // -- new attach files //
@@ -5711,7 +5727,7 @@
 
 .label-editor-wrap {
     .new-label-wrap {
-        margin:40px auto 30px;
+        margin:30px auto;
 
         .form-legend {
             display:block;
@@ -5752,15 +5768,6 @@
         .list-item {
             border-top:1px solid #ddd;
 
-            .category-exclusive {
-                margin-right:5px;
-                vertical-align: middle;
-                cursor:help;
-
-                &.single   { color:@yobi-cyan; }
-                &.multiple { color:@yobi-yello-dark; }
-            }
-
             .category-name {
                 margin-right:2px;
             }
@@ -5794,6 +5801,15 @@
     }
 }
 
+.category-exclusive {
+    margin-right:5px;
+    vertical-align: middle;
+    cursor:help;
+
+    &.single   { color:@yobi-cyan; }
+    &.multiple { color:@yobi-yello-dark; }
+}
+
 .edit-label-form {
     margin-top:20px;
 
app/assets/stylesheets/less/_reponsive.less
--- app/assets/stylesheets/less/_reponsive.less
+++ app/assets/stylesheets/less/_reponsive.less
@@ -119,4 +119,12 @@
              width:980px;
         }
     }
+
+    .issue-info {
+        padding:15px 0 0 10px;
+    }
+
+    .markdown-help .markdown-help-nav li {
+        padding: 5px 8px;
+    }
 }
app/assets/stylesheets/less/_yobiUI.less
--- app/assets/stylesheets/less/_yobiUI.less
+++ app/assets/stylesheets/less/_yobiUI.less
@@ -752,6 +752,7 @@
     &.ybtn-fullsize {
         display:block;
         width:100%;
+        box-sizing:border-box;
     }
 
     &.ybtn-padding {
app/controllers/IssueApp.java
--- app/controllers/IssueApp.java
+++ app/controllers/IssueApp.java
@@ -51,6 +51,7 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Collections;
 import utils.HttpUtil;
 
 public class IssueApp extends AbstractPostingApp {
@@ -106,6 +107,7 @@
         models.support.SearchCondition searchCondition = issueParamForm.bindFromRequest().get();
         searchCondition.pageNum = pageNum - 1;
         searchCondition.labelIds.addAll(LabelSearchUtil.getLabelIds(request()));
+        searchCondition.labelIds.remove(null);
 
         // determine pjax or json when requested with XHR
         if (HttpUtil.isRequestedWithXHR(request())) {
@@ -664,7 +666,9 @@
         String[] labelIds = form.get("labelIds");
         if (labelIds != null) {
             for (String labelId : labelIds) {
-                issue.labels.add(IssueLabel.finder.byId(Long.parseLong(labelId)));
+                if(!StringUtils.isEmpty(labelId)) {
+                    issue.labels.add(IssueLabel.finder.byId(Long.parseLong(labelId)));
+                }
             }
         }
     }
app/controllers/IssueLabelApp.java
--- app/controllers/IssueLabelApp.java
+++ app/controllers/IssueLabelApp.java
@@ -114,14 +114,14 @@
         Project project = Project.findByOwnerAndProjectName(ownerName, projectName);
         List<IssueLabel> labels = IssueLabel.findByProject(project);
 
-        return ok(views.html.issue.partial_labels_list.render(project, labels));
+        return ok(views.html.project.partial_issuelabels_list.render(project, labels));
     }
 
     private static Result labelsAsHTML(String ownerName, String projectName){
         Project project = Project.findByOwnerAndProjectName(ownerName, projectName);
         List<IssueLabel> labels = IssueLabel.findByProject(project);
 
-        return ok(views.html.issue.labels.render(project, labels));
+        return ok(views.html.project.issuelabels.render(project, labels));
     }
 
     /**
app/models/Issue.java
--- app/models/Issue.java
+++ app/models/Issue.java
@@ -413,6 +413,16 @@
         return labels;
     }
 
+    public Set<Long> getLabelIds() {
+        Set<Long> labelIds = new HashSet<>();
+
+        for(IssueLabel label : this.labels){
+            labelIds.add(label.id);
+        }
+
+        return labelIds;
+    }
+
     public List<TimelineItem> getTimeline() {
         List<TimelineItem> timelineItems = new ArrayList<>();
         timelineItems.addAll(comments);
app/utils/LabelSearchUtil.java
--- app/utils/LabelSearchUtil.java
+++ app/utils/LabelSearchUtil.java
@@ -35,6 +35,8 @@
 import com.avaje.ebean.Query;
 import com.avaje.ebean.QueryResultVisitor;
 
+import org.apache.commons.lang3.StringUtils;
+
 public class LabelSearchUtil {
     private static final String FIELD_NAME_ID = "id";
 
@@ -53,7 +55,9 @@
         String[] labelIds = request.queryString().get("labelIds");
         if (labelIds != null) {
             for (String labelId : labelIds) {
-                set.add(Long.valueOf(labelId));
+                if(!StringUtils.isEmpty(labelId)) {
+                    set.add(Long.valueOf(labelId));
+                }
             }
         }
         return set;
 
app/views/common/issueLabel.scala.html (deleted)
--- app/views/common/issueLabel.scala.html
@@ -1,49 +0,0 @@
-@**
-* Yobi, Project Hosting SW
-*
-* Copyright 2013 NAVER Corp.
-* http://yobi.io
-*
-* @Author Jihan Kim
-*
-* Licensed under the Apache License, Version 2.0 (the "License");
-* you may not use this file except in compliance with the License.
-* You may obtain a copy of the License at
-*
-*   http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing, software
-* distributed under the License is distributed on an "AS IS" BASIS,
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-* See the License for the specific language governing permissions and
-* limitations under the License.
-**@
-@()
-<script type="text/javascript" src="@routes.Assets.at("javascripts/common/yobi.Label.js")"></script>
-<script type="text/javascript" src="@routes.Assets.at("javascripts/common/yobi.LabelEditor.js")"></script>
-
-@**<!-- yobi.LabelEditor -->**@
-<script type="text/x-jquery-tmpl" id="tplYobiLabelEditor">
-<div class="control-group label-editor">
-    <strong class="control-label">${labelNew}</strong>
-    <div id="custom-label" class="controls mt5">
-        <div class="row-fluid">
-            <div>
-                <input type="text" name="labelCategory" class="input-small labelInput" data-provider="typeahead" autocomplete="off" placeholder="${labelCategory}">
-            </div>
-            <div>
-                <input type="text" name="labelName" class="input-small labelInput" placeholder="${labelName}" autocomplete="off">
-            </div>
-        </div>
-        <div class="row-fluid colors-wrap">
-            <div class="colors"></div>
-            <div class="colorInput">
-                <input type="text" name="labelColor" class="input-small labelInput labelColor" placeholder="${labelCustomColor}">
-            </div>
-        </div>
-        <div class="row-fluid mt5">
-            <div class="span12"><button type="button" class="ybtn ybtn-default labelSubmit">${labelAdd}</button></div>
-        </div>
-    </div></div>
-</script>
-@**<!-- // yobi.LabelEditor -->**@
app/views/issue/create.scala.html
--- app/views/issue/create.scala.html
+++ app/views/issue/create.scala.html
@@ -89,7 +89,10 @@
                             <dd>
                             @defining(Milestone.findOpenMilestones(project.id)) { milestones =>
                                 @if(milestones.isEmpty()) {
-                                    <a href="@routes.MilestoneApp.newMilestoneForm(project.owner, project.name)" target="_blank" class="ybtn ybtn-success ybtn-small">@Messages("milestone.menu.new")</a>
+                                    <a href="@routes.MilestoneApp.newMilestoneForm(project.owner, project.name)"
+                                       class="ybtn ybtn-small ybtn-fullsize" target="_blank">
+                                        @Messages("milestone.menu.new")
+                                    </a>
                                 } else {
                                     <select id="milestoneId" name="milestoneId"
                                             data-toggle="select2" data-format="milestone">
@@ -116,30 +119,7 @@
                         </dd>
                     </dl>
 
-                    <dl id="labels" class="issue-option">
-                        <dt>
-                            @Messages("label")
-                            @if(isProjectResourceCreatable(UserApp.currentUser, project, ResourceType.ISSUE_LABEL)){
-                            <a href="@routes.IssueLabelApp.labels(project.owner, project.name)" target="_blank"
-                               class="ybtn ybtn-default ybtn-mini pull-right"><i class="yobicon-cog"></i></a>
-                            }
-                        </dt>
-                        <dd>
-                            <select id="issueLabels" multiple="multiple" data-toggle="select2" data-format="issuelabel"
-                                    data-dropdown-css-class="issue-labels" data-container-css-class="issue-labels" style="border:1px solid #ddd;">
-                                <option></option>
-                                @IssueLabel.findByProject(project).groupBy(_.category).map {
-                                    case (category, labels) => {
-                                        <optgroup label="@category.name">
-                                        @labels.map { label =>
-                                            <option value="@label.id" data-category="@category.id">@label.name</option>
-                                        }
-                                        </optgroup>
-                                    }
-                                }
-                            </select>
-                        </dd>
-                    </dl>
+                    @partial_select_label(IssueLabel.findByProject(project), null, "issue-option")
                 </div>
             </div>
         </div>
app/views/issue/edit.scala.html
--- app/views/issue/edit.scala.html
+++ app/views/issue/edit.scala.html
@@ -110,7 +110,10 @@
                         <dd>
                         @defining(issue.milestone != null) { hasMilestone =>
                             @if(Milestone.findByProjectId(project.id).isEmpty()) {
-                                <a href="@routes.MilestoneApp.newMilestoneForm(project.owner, project.name)" target="_blank" class="ybtn ybtn-success ybtn-small">@Messages("milestone.menu.new")</a>
+                                <a href="@routes.MilestoneApp.newMilestoneForm(project.owner, project.name)"
+                                   class="ybtn ybtn-small ybtn-fullsize" target="_blank">
+                                    @Messages("milestone.menu.new")
+                                </a>
                             } else {
                                 <select id="milestoneId" name="milestoneId"
                                             data-toggle="select2" data-format="milestone">
@@ -154,32 +157,7 @@
                         </dd>
                     </dl>
 
-                    <dl id="labels" class="issue-option">
-                        <dt>
-                            @Messages("label")
-                            @if(isProjectResourceCreatable(UserApp.currentUser, project, ResourceType.ISSUE_LABEL)){
-                            <a href="@routes.IssueLabelApp.labels(project.owner, project.name)" target="_blank"
-                               class="ybtn ybtn-default ybtn-mini pull-right"><i class="yobicon-cog"></i></a>
-                            }
-                        </dt>
-                        <dd>
-                            <select id="issueLabels" multiple="multiple" data-toggle="select2" data-format="issuelabel"
-                                    data-dropdown-css-class="issue-labels" data-container-css-class="issue-labels" style="border:1px solid #ddd;">
-                                <option></option>
-                                @IssueLabel.findByProject(project).groupBy(_.category).map {
-                                    case (category, labels) => {
-                                        <optgroup label="@category.name">
-                                        @labels.map { label =>
-                                            <option value="@label.id" data-category="@category.id" @if(issue.labels.contains(label)){ selected }>
-                                        @label.name
-                                        </option>
-                                        }
-                                        </optgroup>
-                                    }
-                                }
-                            </select>
-                        </dd>
-                    </dl>
+                    @partial_select_label(IssueLabel.findByProject(project), issue.getLabelIds, "issue-option")
                 </div>
             </div>
         </div>
app/views/issue/my_list.scala.html
--- app/views/issue/my_list.scala.html
+++ app/views/issue/my_list.scala.html
@@ -35,43 +35,12 @@
             @views.html.issue.my_partial_search(title, currentPage, param, project)
         </div>
     </div>
-    <div id="spin" style="position:absolute; top:35%; left:50%"></div>
-    <script type="text/javascript" src="/assets/javascripts/lib/spin.js"></script>
     <script type="text/javascript">
-        $(document).ready(function(){
-            $(".issue-label").on('click', function(event) {
-                window.location.href = $(this).attr("href");
-            });
-            var pjaxOptions = {
-                fragment:"div[pjax-container]",
-                timeout:3000
-            };
-
-            if($.support.pjax) {
-                $.pjax.defaults.maxCacheLength = 0;
-            }
-
-            $(document).on('click', 'a[pjax-page]', function(event) {
-                $.pjax.click(event, "div[pjax-container]", pjaxOptions)
-            });
-
-            $(document).on("submit", "form[name='search']", function(event) {
-    			$.pjax.submit(event, "div[pjax-container]", pjaxOptions)
-    		});
-
-            $(document).on('pjax:send', function() {
-                yobi.ui.Spinner.show();
-            });
-
-            $(document).on('pjax:complete', function() {
-                yobi.ui.Spinner.hide();
-            });
-            $(document).on('pjax:end', function() {
-                $(".issue-label").on('click', function() {
-                    window.location.href = $(this).attr("href");
-                });
-            });
-
+      $(function(){
+        $yobi.loadModule("issue.List", {
+          "welSearchForm": $("#search"),
+          "elPagination" : $("#pagination")
         });
+      });
     </script>
 }
app/views/issue/my_partial_list.scala.html
--- app/views/issue/my_partial_list.scala.html
+++ app/views/issue/my_partial_list.scala.html
@@ -82,7 +82,7 @@
                 }
 
                 @for(label <- issue.labels.toList.sortBy(r => (r.category.name, r.name))) {
-                    <a href="@urlToList(issue.project, searchCondition.state)&labelIds=@label.id" class="label issue-label list-label" data-label-id="@label.id" data-color="@label.color" style="background:@label.color">@label.name</a>
+                    <a href="@urlToList(issue.project, searchCondition.state)&labelIds=@label.id" class="label issue-label list-label" data-label-id="@label.id" style="background:@label.color">@label.name</a>
                 }
             </div>
         </div>
 
app/views/issue/my_partial_list_quicksearch.scala.html (added)
+++ app/views/issue/my_partial_list_quicksearch.scala.html
@@ -0,0 +1,53 @@
+@**
+* Yobi, Project Hosting SW
+*
+* Copyright 2014 NAVER Corp.
+* http://yobi.io
+*
+* @Author Jihan Kim
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*   http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+**@
+@(param:models.support.SearchCondition)
+
+<ul class="lst-stacked unstyled">
+    @if(!UserApp.currentUser().isAnonymous()){
+    <li @if(param.assigneeId == UserApp.currentUser().id){ class="active"}>
+        <a pjax-filter href="#"
+            data-author-id=""
+            data-assignee-id="@User.findByLoginId(session.get("loginId")).id"
+            data-milestone-id="@param.milestoneId"
+            data-mention-id="">
+            @Messages("issue.list.assignedToMe")
+        </a>
+    </li>
+    <li @if(param.authorId == UserApp.currentUser().id){ class="active"}>
+        <a pjax-filter href="#"
+            data-author-id="@User.findByLoginId(session.get("loginId")).id"
+            data-assignee-id=""
+            data-milestone-id="@param.milestoneId"
+            data-mention-id="">
+            @Messages("issue.list.authoredByMe")
+        </a>
+    </li>
+    <li @if(param.mentionId == UserApp.currentUser().id){ class="active"}>
+        <a pjax-filter href="#"
+            data-author-id=""
+            data-assignee-id=""
+            data-milestone-id="@param.milestoneId"
+            data-mention-id="@User.findByLoginId(session.get("loginId")).id">
+            @Messages("issue.list.mentionedOfMe")
+        </a>
+    </li>
+    }
+</ul>
app/views/issue/my_partial_search.scala.html
--- app/views/issue/my_partial_search.scala.html
+++ app/views/issue/my_partial_search.scala.html
@@ -21,133 +21,81 @@
 @(title: String, currentPage: com.avaje.ebean.Page[Issue], param:models.support.SearchCondition, project:Project)
 
 @import helper._
-@import utils.TemplateHelper._
-@import utils.AccessControl._
-@import scala.collection.immutable.Map
-@import scala.collection.mutable.ArrayBuffer
 @import models.enumeration._
-@import org.apache.commons.lang.StringUtils
-@import utils.HttpUtil._
-
-@urlToList = {@routes.IssueApp.issues(project.owner, project.name, param.state, "html", currentPage.getPageIndex + 1)}
-@paramForEveryone = @{ param.clone().setState(param.state).setAssigneeId(null).setAuthorId(null) }
-@paramForIssuesAssignedToMe = @{ param.clone().setState(param.state).setAssigneeId(UserApp.currentUser().id).setAuthorId(null) }
-@paramForIssuesAuthoredByMe = @{ param.clone().setState(param.state).setAssigneeId(null).setAuthorId(UserApp.currentUser().id) }
-
-@getPageListUrl(pageIndex:Integer) = {@routes.IssueApp.userIssues(param.state, "html", pageIndex + 1)}
+@import utils.AccessControl._
+@import utils.TemplateHelper._
 
 @makeFilterLink(fieldName:String, orderBy:String, orderDir:String, fieldText:String) = {
 	@if(orderBy.equals(fieldName)) {
 		<a href="#" orderBy="@orderBy" orderDir="@if(orderDir.equals("desc")){asc}else{desc}" class="filter active"><i class="ico btn-gray-arrow @if(orderDir.equals("desc")){ down }"></i>@fieldText</a>
 	} else {
-	    <a href="#" orderBy="@fieldName" orderDir="desc" class="filter"><i class="ico btn-gray-arrow down"></i>@fieldText</a>
+    <a href="#" orderBy="@fieldName" orderDir="desc" class="filter"><i class="ico btn-gray-arrow down"></i>@fieldText</a>
 	}
 }
 
-@getTabLinkByState(state:State) = {@routes.IssueApp.issues(project.owner, project.name, state.state)@if(param.assigneeId != null){?assigneeId=@param.assigneeId}@if(param.authorId != null){&authorId=@param.authorId}@if(param.milestoneId != null){&milestoneId=@param.milestoneId}}
+<div pjax-container class="row-fluid issue-list-wrap">
+    <div class="span2">
+        <div class="inner advanced">
+            @my_partial_list_quicksearch(param)
 
-	<div pjax-container class="row-fluid issue-list-wrap">
-        <div class="span2">
-            <div class="inner advanced">
-                <ul class="lst-stacked unstyled">
-                    @if(!UserApp.currentUser().isAnonymous()){
-                    <li @if(param.assigneeId == UserApp.currentUser().id){ class="active"}>
-                        <a pjax-filter href="#" mentionId="" authorId="" assigneeId="@User.findByLoginId(session.get("loginId")).id" milestoneId="@param.milestoneId">@Messages("issue.list.assignedToMe")
-                        </a>
-                    </li>
-                    <li @if(param.authorId == UserApp.currentUser().id){ class="active"}>
-                        <a pjax-filter href="#" mentionId="" authorId="@User.findByLoginId(session.get("loginId")).id" assigneeId="" milestoneId="@param.milestoneId">@Messages("issue.list.authoredByMe")
-                        </a>
-                    </li>
-                    <li @if(param.mentionId == UserApp.currentUser().id){ class="active"}>
-                        <a pjax-filter href="#" mentionId="@User.findByLoginId(session.get("loginId")).id" authorId="" assigneeId="" milestoneId="@param.milestoneId">@Messages("issue.list.mentionedOfMe")
-                        </a>
-                    </li>
-                    }
-                </ul>
+            <hr>
 
-                @if(param.milestoneId != null){
-                    @defining(Milestone.findById(param.milestoneId)){ milestone =>
-                        @if(milestone != null){
-                            <hr>
-                            @views.html.milestone.partial_status(milestone, project)
-                        }
+            <form id="search" name="search" action="@routes.IssueApp.userIssues()" method="get">
+                <input type="hidden" name="orderBy" value="@param.orderBy">
+                <input type="hidden" name="orderDir" value="@param.orderDir">
+                <input type="hidden" name="state" value="@param.state">
+                <input type="hidden" name="authorId" value="@param.authorId" data-search="authorId">
+                <input type="hidden" name="assigneeId" value="@param.assigneeId" data-search="assigneeId">
+                <input type="hidden" name="mentionId" value="@param.mentionId" data-search="mentionId">
+                <div class="search">
+                    <div class="search-bar">
+                        <input name="filter" class="textbox full" type="text" value="@param.filter">
+                        <button type="submit" class="search-btn"><i class="yobicon-search"></i></button>
+                    </div>
+                </div>
+            </form>
+
+            @if(param.milestoneId != null){
+                @defining(Milestone.findById(param.milestoneId)){ milestone =>
+                    @if(milestone != null){
+                        <hr>
+                        @views.html.milestone.partial_status(milestone, project)
                     }
                 }
-
-                <form id="search" name="search" action="@routes.IssueApp.userIssues()" method="get">
-                    <input type="hidden" name="orderBy" value="@param.orderBy">
-                    <input type="hidden" name="orderDir" value="@param.orderDir">
-                    <input type="hidden" name="state" value="@param.state">
-                    <input type="hidden" name="authorId" value="@param.authorId">
-                    <input type="hidden" name="assigneeId" value="@param.assigneeId">
-                    <input type="hidden" name="mentionId" value="@param.mentionId">
-                    <hr>
-                    <div class="search">
-                        <div class="search-bar">
-                            <input name="filter" class="textbox full" type="text" value="@param.filter">
-                            <button type="submit" class="search-btn"><i class="yobicon-search"></i></button>
-                        </div>
-                    </div>
-                </form>
-            </div>
-        </div>
-        <div class="span10" id="span10">
-            <ul class="nav nav-tabs nm">
-            @for(state <- Array(State.OPEN, State.CLOSED)) {
-                <li @if(param.state == state.state) { class="active" } data-pjax>
-                    <a href="#" state="@state.state">
-                        @Messages("issue.state." + state.name.toLowerCase)
-                        <span class="num-badge">@Issue.countIssuesBy(param.clone.setState(state))</span>
-                    </a>
-                </li>
             }
-            </ul>
-
-
-        @if(currentPage.getList.size() > 0){
-		<div class="filter-wrap small-heights">
+        </div>
+    </div>
+    <div class="span10" id="span10">
+        <ul class="nav nav-tabs nm">
+        @for(state <- Array(State.OPEN, State.CLOSED)) {
+            <li @if(param.state == state.state) { class="active" } data-pjax>
+                <a href="#" state="@state.state">
+                    @Messages("issue.state." + state.name.toLowerCase)
+                    <span class="num-badge">@Issue.countIssuesBy(param.clone.setState(state))</span>
+                </a>
+            </li>
+        }
+        </ul>
+        @if(!currentPage.getList.isEmpty){
+            <div class="filter-wrap small-heights">
                 @if(currentPage.getList.size() > 1){
-			<div class="filters pull-right">
-                @makeFilterLink("dueDate", param.orderBy, param.orderDir, Messages("common.order.dueDate"))
-				@makeFilterLink("updatedDate", param.orderBy, param.orderDir, Messages("common.order.updatedDate"))
-				@makeFilterLink("createdDate", param.orderBy, param.orderDir, Messages("common.order.date"))
-				@makeFilterLink("numOfComments", param.orderBy, param.orderDir, Messages("common.order.comments"))
-			</div>
-			}
-		</div>
+
+                <div class="filters pull-right">
+                  @makeFilterLink("updatedDate", param.orderBy, param.orderDir, Messages("common.order.updatedDate"))
+                  @makeFilterLink("createdDate", param.orderBy, param.orderDir, Messages("common.order.date"))
+                  @makeFilterLink("numOfComments", param.orderBy, param.orderDir, Messages("common.order.comments"))
+                </div>
+                }
+            </div>
 
             @my_partial_list(currentPage.getList, param, currentPage.getPageIndex, currentPage.getTotalPageCount, project)
 
-            <div class="pull-left" style="padding:10px;">
-            </div>
-            <div id="pagination"><!-- pagination.js will fill here. --></div>
+            <div id="pagination" data-total="@currentPage.getTotalPageCount"><!-- pagination.js will fill here. --></div>
         } else {
             <div class="error-wrap">
                 <i class="ico ico-err1"></i>
                 <p>@Messages("issue.is.empty")</p>
             </div>
         }
-        </div>
-		<script type="text/javascript">
-			$(document).ready(function(){
-                $("a[pjax-filter]" ).bind("click",function(){
-                    $("input[name='authorId']").val($(this).attr("authorId"));
-                    $("input[name='assigneeId']").val($(this).attr("assigneeId"));
-                    $("input[name='mentionId']").val($(this).attr("mentionId"));
-                    $("#search" ).submit();
-                });
-				$yobi.loadModule("issue.List", {
-                "welSearchOrder": $("a[orderBy]"),
-                "welSearchState": $("a[state]"),
-                "welSearchAuthor": $("div[data-name='authorId']"),
-                "welSearchAssignee": $("div[data-name='assigneeId']"),
-                "welSearchMilestone": $("div[data-name='milestoneId']"),
-                "welSearchForm":$("form[name='search']"),
-                "welFilter": $("a[pjax-filter]"),
-                "elPagination": $("#pagination"),
-                "nTotalPages" : @currentPage.getTotalPageCount
-				});
-			});
-		</script>
     </div>
+</div>
app/views/issue/partial_assignee.scala.html
--- app/views/issue/partial_assignee.scala.html
+++ app/views/issue/partial_assignee.scala.html
@@ -23,6 +23,7 @@
 @import utils.AccessControl._
 
 <select id="assignee" name="assignee.user.id"
+        data-field-name="assignee.id"
         data-toggle="select2" data-format="user">
     <option value="@User.anonymous.id" @if(issue == null || issue.assignee == null){selected}>@Messages("issue.noAssignee")</option>
     @if(isAllowed(UserApp.currentUser(), project.asResource(), Operation.ASSIGN_ISSUE)) {
app/views/issue/partial_list.scala.html
--- app/views/issue/partial_list.scala.html
+++ app/views/issue/partial_list.scala.html
@@ -85,7 +85,7 @@
                     }
 
                     @for(label <- issue.labels.toList.sortBy(r => (r.category.name, r.name))) {
-                        <a href="#" class="label issue-label list-label" data-label-id="@label.id" data-color="@label.color" style="background:@label.color">@label.name</a>
+                        <a href="#" class="label issue-label list-label" data-category-id="@label.category.id" data-label-id="@label.id" data-color="@label.color" style="background:@label.color">@label.name</a>
                     }
                 </div>
             </label>
app/views/issue/partial_list_quicksearch.scala.html
--- app/views/issue/partial_list_quicksearch.scala.html
+++ app/views/issue/partial_list_quicksearch.scala.html
@@ -26,18 +26,30 @@
 
 <ul class="lst-stacked unstyled">
     <li @if(param.assigneeId == null && param.authorId == null){class="active"}>
-        <a pjax-filter href="#" authorId="" assigneeId="" milestoneId="@param.milestoneId">@Messages("issue.list.all")
+        <a pjax-filter href="#"
+           data-assignee-id=""
+           data-author-id=""
+           data-milestone-id="@param.milestoneId">
+            @Messages("issue.list.all")
             <span class="num-badge pull-right">@Issue.countIssuesBy(project.id, paramForEveryone)</span>
         </a>
     </li>
     @if(!UserApp.currentUser().isSiteManager){
     <li @if(param.assigneeId == UserApp.currentUser().id){ class="active"}>
-        <a pjax-filter href="#" authorId="" assigneeId="@User.findByLoginId(session.get("loginId")).id" milestoneId="@param.milestoneId">@Messages("issue.list.assignedToMe")
+        <a pjax-filter href="#"
+           data-assignee-id="@User.findByLoginId(session.get("loginId")).id"
+           data-author-id=""
+           data-milestone-id="@param.milestoneId">
+            @Messages("issue.list.assignedToMe")
             <span class="num-badge pull-right">@Issue.countIssuesBy(project.id, paramForIssuesAssignedToMe)</span>
         </a>
     </li>
     <li @if(param.authorId == UserApp.currentUser().id){ class="active"}>
-        <a pjax-filter href="#" authorId="@User.findByLoginId(session.get("loginId")).id" assigneeId="" milestoneId="@param.milestoneId">@Messages("issue.list.authoredByMe")
+        <a pjax-filter href="#"
+           data-assignee-id=""
+           data-author-id="@User.findByLoginId(session.get("loginId")).id"
+           data-milestone-id="@param.milestoneId">
+            @Messages("issue.list.authoredByMe")
             <span class="num-badge pull-right">@Issue.countIssuesBy(project.id, paramForIssuesAuthoredByMe)</span>
         </a>
     </li>
app/views/issue/partial_massupdate.scala.html
--- app/views/issue/partial_massupdate.scala.html
+++ app/views/issue/partial_massupdate.scala.html
@@ -72,7 +72,7 @@
 
         <div id="assignee" class="btn-group" data-name="assignee.id">
             <button class="btn dropdown-toggle medium" data-toggle="dropdown" disabled="disabled">
-                <span class="d-label">@Messages("issue.update.assignee")</span>
+                <span class="d-label">@Messages("issue.update.assignee.id")</span>
                 <span class="d-caret"><span class="caret"></span></span>
             </button>
             <ul class="dropdown-menu mass-update-list">
@@ -98,7 +98,7 @@
         @if(project.menuSetting.milestone) {
         <div id="milestone" class="btn-group" data-name="milestone.id">
             <button class="btn dropdown-toggle medium" data-toggle="dropdown" disabled="disabled">
-                <span class="d-label">@Messages("issue.update.milestone")</span>
+                <span class="d-label">@Messages("issue.update.milestone.id")</span>
                 <span class="d-caret"><span class="caret"></span></span>
             </button>
             <ul class="dropdown-menu mass-update-list">
app/views/issue/partial_searchform.scala.html
--- app/views/issue/partial_searchform.scala.html
+++ app/views/issue/partial_searchform.scala.html
@@ -29,7 +29,7 @@
     <hr>
     <div class="search">
         <div class="search-bar">
-            <input name="filter" class="textbox full" type="text" value="@param.filter">
+            <input name="filter" class="textbox full" type="text" value="@param.filter" data-search="filter">
             <button type="submit" class="search-btn"><i class="yobicon-search"></i></button>
         </div>
     </div>
@@ -38,7 +38,7 @@
         <dl class="issue-option">
             <dt>@Messages("issue.author")</dt>
             <dd>
-                <select id="authorId" name="authorId" data-toggle="select2" data-format="user">
+                <select id="authorId" name="authorId" data-toggle="select2" data-format="user" data-search="authorId">
                     <option value="">@Messages("common.order.all")</option>
                     @if(ProjectUser.isMember(UserApp.currentUser().id, project.id)){
                     <option value="@UserApp.currentUser().id">@Messages("issue.list.authoredByMe")</option>
@@ -60,7 +60,7 @@
         <dl class="issue-option">
             <dt>@Messages("issue.assignee")</dt>
             <dd>
-                <select id="assigneeId" name="assigneeId" data-toggle="select2" data-format="user">
+                <select id="assigneeId" name="assigneeId" data-toggle="select2" data-format="user" data-search="assigneeId">
                     <option value="">@Messages("common.order.all")</option>
                     <option value="@User.anonymous.id" @if(param.assigneeId != null && param.assigneeId == User.anonymous.id){ selected }>@Messages("issue.noAssignee")</option>
                     @if(ProjectUser.isMember(UserApp.currentUser().id, project.id)){
@@ -84,7 +84,7 @@
         <dl class="issue-option">
             <dt>@Messages("milestone")</dt>
             <dd>
-                <select id="milestoneId" name="milestoneId" data-toggle="select2" data-format="milestone">
+                <select id="milestoneId" name="milestoneId" data-toggle="select2" data-format="milestone" data-search="milestoneId">
                     <option value="">@Messages("milestone.state.all")</option>
                     <option value="@Milestone.NULL_MILESTONE_ID"
                         @if(param.milestoneId != null && param.milestoneId == Milestone.NULL_MILESTONE_ID){
@@ -128,36 +128,22 @@
         <dl class="issue-option">
             <dt>@Messages("issue.dueDate")</dt>
             <dd class="search search-bar">
-                <input type="text" id="issueDueDate" data-toggle="calendar" name="dueDate" class="textbox full" value="@param.getDueDateString">
+                <input id="issueDueDate" type="text" name="dueDate" class="textbox full" value="@param.getDueDateString" data-toggle="calendar">
                 <button type="button" class="search-btn"><i class="yobicon-calendar2"></i></button>
             </dd>
         </dl>
 
-        <dl id="labels" class="issue-option labels-wrap">
-            <dt>
-                @Messages("label")
-                @if(isProjectResourceCreatable(UserApp.currentUser, project, ResourceType.ISSUE_LABEL)){
-                <a href="@routes.IssueLabelApp.labels(project.owner, project.name)" target="_blank"
-                   class="ybtn ybtn-default ybtn-mini pull-right"><i class="yobicon-cog"></i></a>
-                }
-            </dt>
-            <dd>
-                <select id="labelIds" name="labelIds" multiple="multiple" data-toggle="select2" data-format="issuelabel"
-                    data-dropdown-css-class="issue-labels" data-container-css-class="issue-labels" style="border:1px solid #ddd;">
-                    <option></option>
-                    @IssueLabel.findByProject(project).groupBy(_.category).map {
-                        case (category, labels) => {
-                            <optgroup label="@category.name">
-                            @labels.map { label =>
-                                <option value="@label.id" data-category="@category.id" @if(param.labelIds != null && param.labelIds.contains(label.id)){ selected }>
-                            @label.name
-                            </option>
-                            }
-                            </optgroup>
-                        }
-                    }
-                </select>
-            </dd>
-        </dl>
+        @defining(IssueLabel.findByProject(project)){ labels =>
+        <div class="labels-wrap">
+            @if(isProjectResourceCreatable(UserApp.currentUser, project, ResourceType.ISSUE_LABEL)){
+            <a href="@routes.IssueLabelApp.labels(project.owner, project.name)" class="ybtn ybtn-default ybtn-mini pull-right">
+                <i class="yobicon-cog vmiddle"></i>
+                @if(labels.isEmpty){<span class="vmiddle" style="margin-left:2px;">@Messages("label.manage")</span>}
+            </a>
+            }
+
+            @partial_select_label(labels, param.labelIds, "issue-option")
+        </div>
+        }
     </div>
 </form>
 
app/views/issue/partial_select_label.scala.html (added)
+++ app/views/issue/partial_select_label.scala.html
@@ -0,0 +1,53 @@
+@**
+* Yobi, Project Hosting SW
+*
+* Copyright 2014 NAVER Corp.
+* http://yobi.io
+*
+* @Author Jihan Kim
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*   http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+**@
+@(labels:List[models.IssueLabel], selectedLabelIds:Set[Long] = null, wrapperCSSClassName:String = "")
+
+@if(!labels.isEmpty){
+<dl class="@wrapperCSSClassName">
+  <dt>
+    @Messages("label")
+  </dt>
+  <dd>
+    <select name="labelIds" multiple="multiple" data-search="labelIds"
+            data-toggle="select2" data-format="issuelabel" data-allow-clear="true" data-close-on-select="false"
+            data-dropdown-css-class="issue-labels" data-container-css-class="issue-labels bordered"
+            data-placeholder="@Messages("label.select")">
+      <option></option>
+      @labels.groupBy(_.category).map {
+        case (category, labels) => {
+        <optgroup label="@category.name"
+                  data-category-id="@category.id"
+                  data-category-is-exclusive="@category.isExclusive">
+          @labels.map { label =>
+          <option value="@label.id"
+                  data-category-id="@category.id"
+                  data-category-is-exclusive="@category.isExclusive"
+                  @if(selectedLabelIds != null && selectedLabelIds.contains(label.id)){ selected }>
+            @label.name
+          </option>
+          }
+        </optgroup>
+        }
+      }
+    </select>
+  </dd>
+</dl>
+}
 
app/views/issue/partial_show_selected_label.scala.html (added)
+++ app/views/issue/partial_show_selected_label.scala.html
@@ -0,0 +1,37 @@
+@**
+* Yobi, Project Hosting SW
+*
+* Copyright 2014 NAVER Corp.
+* http://yobi.io
+*
+* @Author Jihan Kim
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*   http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+**@
+@(labels:List[models.IssueLabel], listLink:String = "")
+
+@if(!labels.isEmpty){
+    <dl>
+        <dt>
+            @Messages("label")
+        </dt>
+        <dd>
+            @for(label <- labels){
+                <a href='@listLink&labelIds=@label.id' class="label issue-label active static"
+                   data-label-id="@label.id" style="background:@label.color">
+                    @label.name
+                </a>
+            }
+        </dd>
+    </dl>
+}
app/views/issue/view.scala.html
--- app/views/issue/view.scala.html
+++ app/views/issue/view.scala.html
@@ -76,65 +76,106 @@
         <!--board-body-->
         <div class="board-body row-fluid">
             <div class="span9">
-                @if(StringUtils.isEmpty(issue.body)){
-                <div class="content empty-content"></div>
-                } else {
-                <div class="markdown-loader">
-                    <i class=" yobicon-loading2"></i> Loading....
+                <div class="author-info">
+                    <a href="@routes.UserApp.userInfo(issue.authorLoginId)" class="usf-group">
+                        <span class="avatar-wrap smaller">
+                            <img src="@User.findByLoginId(issue.authorLoginId).avatarUrl" width="20" height="20">
+                        </span>
+                        @if(issue.authorLoginId != null){
+                        <strong class="name">@issue.authorName</strong>
+                        <span class="loginid"> <strong>@{"@"}</strong>@issue.authorLoginId</span>
+                        } else {
+                        <strong class="name">@Messages("issue.noAuthor")</strong>
+                        }
+                    </a>
                 </div>
-                <div class="content markdown-wrap markdown-before" markdown="true">@issue.body</div>
+
+                @if(StringUtils.isEmpty(issue.body)){
+                    <div class="content empty-content"></div>
+                } else {
+                    <div class="markdown-loader">
+                        <i class="yobicon-loading2"></i> @Messages("site.massMail.loading")
+                    </div>
+                    <div class="content markdown-wrap markdown-before" markdown="true">@issue.body</div>
                 }
                 <div class="attachments" id="attachments" data-resource-type="@ResourceType.ISSUE_POST" data-resource-id="@issue.id"></div>
+
+                <div class="board-actrow right-txt">
+                    <div class="pull-left">
+                        <div>
+                            @if(isAllowed(UserApp.currentUser(), issue.asResource(), Operation.WATCH)) {
+                                <button id="watch-button" type="button" class="ybtn @if(issue.getWatchers.contains(UserApp.currentUser())) {ybtn-watching}"
+                                        data-toggle="button" data-watching="@if(issue.getWatchers.contains(UserApp.currentUser())){true}else{false}">
+                                @if(issue.getWatchers.contains(UserApp.currentUser())) {
+                                    @Messages("project.unwatch")
+                                } else {
+                                    @Messages("project.watch")
+                                }
+                                </button>
+                            }
+
+                            <div id="vote" class="vote-wrap">
+                                @if(isResourceCreatable(User.findByLoginId(session.get("loginId")), issue.asResource(), ResourceType.ISSUE_COMMENT)) {
+                                    <a href="@urlToVote" class="ybtn @if(issue.isVotedBy(UserApp.currentUser)){ybtn-watching}" title="@getVoteButtonTitle"
+                                       data-request-method="post" data-toggle="tooltip">
+                                        <span class="heart"><i class="yobicon-hearts"></i></span>
+                                        <span class="desc">@Messages("issue.vote")</span>
+                                        @if(issue.voters.size > 0) {
+                                            <strong class="count">@issue.voters.size</strong>
+                                        }
+                                    </a>
+                                } else {
+                                    <span class="ybtn ybtn-disabled" style="color:#777;" data-toggle="tooltip" title="@Messages("user.login.alert")" data-login="required">
+                                        <span class="heart"><i class="yobicon-hearts"></i></span>
+                                        <span class="desc">@Messages("issue.vote")</span>
+                                        @if(issue.voters.size > 0) {
+                                            <strong class="count">@issue.voters.size</strong>
+                                        }
+                                    </span>
+                                }
+
+                                @if(issue.voters.size > 0){
+                                    @partial_voters(issue, 3)
+                                }
+                            </div>
+                        </div>
+                    </div>
+
+                    @if(issue.canBeDeleted) {
+                        @if(isAllowed(UserApp.currentUser(), issue.asResource(), Operation.DELETE)) {
+                            <a href="#deleteConfirm" data-toggle="modal" class="ybtn ybtn-danger">@Messages("button.delete")</a>
+                        }
+                    } else {
+                        <button class="ybtn ybtn-disabled" data-toggle="tooltip" data-placement="top" title="@Messages("issue.can.not.be.deleted")">@Messages("button.delete")</button>
+                    }
+
+                    @if(isAllowed(UserApp.currentUser(), issue.asResource(), Operation.UPDATE)) {
+                        <a href="@routes.IssueApp.editIssueForm(project.owner, project.name, issue.getNumber)" class="ybtn">@Messages("button.edit")</a>
+                    }
+                </div>
+
+                @** Comment **@
+                <div id="comments" class="board-comment-wrap">
+                    <div id="timeline">
+                        <div class="timeline-list">
+                        @partial_comments(project, issue)
+                        </div>
+                    </div>
+                    @common.commentForm(issue.asResource(), ResourceType.ISSUE_COMMENT, routes.IssueApp.newComment(project.owner, project.name, issue.getNumber).toString())
+                </div>
+                @** // Comment **@
             </div>
 
             <div class="span3 mb20">
-                <div class="author-info pull-right mr10">
-                    <form id="issueUpdateForm" action="@routes.IssueApp.massUpdate(project.owner, project.name)" method="post" class="frm-wrap">
+                <div class="issue-info">
+                    <form id="issueUpdateForm" action="@routes.IssueApp.massUpdate(project.owner, project.name)" method="post">
                         <input type="hidden" name="issues[0].id" value="@issue.id" />
-
-                        @**<!-- author  -->**@
-                        <dl class="author">
-                            <dt>@Messages("issue.author")</dt>
-                            <dd style="padding:5px 10px;">
-                                <a href="@routes.UserApp.userInfo(issue.authorLoginId)" class="usf-group">
-                                    <span class="avatar-wrap smaller">
-                                        <img src="@User.findByLoginId(issue.authorLoginId).avatarUrl" width="20" height="20">
-                                    </span>
-                                    @if(issue.authorLoginId != null){
-                                    <strong class="name">@issue.authorName</strong>
-                                    <span class="loginid"> <strong>@{"@"}</strong>@issue.authorLoginId</span>
-                                    } else {
-                                    <strong class="name">@Messages("issue.noAuthor")</strong>
-                                    }
-                                </a>
-                            </dd>
-                        </dl>
-                        @**<!-- // -->**@
-
-                        @**<!-- state  -->**@
-                        <!--
-                        <dl>
-                            <dt>@Messages("issue.state")</dt>
-                            <dd>
-                                <div id="state" class="btn-group" data-name="state">
-                                    <button class="btn dropdown-toggle large" data-toggle="dropdown">
-                                        <span class="d-label">@Messages("issue.state")</span>
-                                        <span class="d-caret"><span class="caret"></span></span>
-                                    </button>
-                                    <ul class="dropdown-menu">
-                                        <li data-value="@State.OPEN.name" @if(issue.state == State.OPEN){data-selected="true" class="active"}><a>@Messages("issue.state.open")</a></li>
-                                        <li data-value="@State.CLOSED.name" @if(issue.state == State.CLOSED){data-selected="true" class="active"}><a>@Messages("issue.state.closed")</a></li>
-                                    </ul>
-                                </div>
-                            </dd>
-                        </dl>
-                        -->
 
                         @**<!-- assignee  -->**@
                         <dl>
                             <dt>@Messages("issue.assignee")</dt>
 
-                            <dd style="padding:5px 10px;">
+                            <dd>
                             @defining(issue.assigneeName != null) { isAssigned =>
                                 @if(isAllowed(UserApp.currentUser(), issue.asResource(), Operation.UPDATE)) {
                                     @partial_assignee(project, issue)
@@ -162,13 +203,12 @@
                         @if(project.menuSetting.milestone) {
                         <dl>
                             <dt>@Messages("milestone")</dt>
-                            <dd style="padding:5px 10px;">
+                            <dd>
                             @if(Milestone.findByProjectId(project.id).isEmpty()){
-                                @if(isAllowed(UserApp.currentUser(), issue.asResource(), Operation.UPDATE)) {
-                                <a href="@routes.MilestoneApp.newMilestoneForm(project.owner, project.name)" target="_blank" class="ybtn ybtn-success ybtn-small">@Messages("milestone.menu.new")</a>
-                                } else {
-                                    @Messages("issue.noMilestone")
-                                }
+                                <a href="@routes.MilestoneApp.newMilestoneForm(project.owner, project.name)"
+                                   class="ybtn ybtn-small ybtn-fullsize" target="_blank">
+                                    @Messages("milestone.menu.new")
+                                </a>
                             } else {
                                 @defining(issue.milestone != null) { hasMilestone =>
                                     @if(isAllowed(UserApp.currentUser(), issue.asResource(), Operation.UPDATE)) {
@@ -213,7 +253,7 @@
                         }
                         @**<!-- // -->**@
 
-                        <dl class="issue-option">
+                        <dl>
                             <dt>
                                 @Messages("issue.dueDate")
                                 @if(issue.dueDate != null) {
@@ -226,10 +266,10 @@
                                 </span>
                                 }
                             </dt>
-                            <dd style="padding:5px 10px;">
+                            <dd>
                                 @if(isAllowed(UserApp.currentUser(), issue.asResource(), Operation.UPDATE)) {
                                 <div class="search search-bar">
-                                    <input type="text" id="issueDueDate" data-toggle="calendar" data-oDueDate="@issue.getDueDateString" name="dueDate" class="textbox full" value="@issue.getDueDateString">
+                                    <input type="text" name="dueDate" value="@issue.getDueDateString" class="textbox full" autocomplete="off" data-toggle="calendar">
                                     <button type="button" class="search-btn"><i class="yobicon-calendar2"></i></button>
                                 </div>
                                 } else {
@@ -244,36 +284,11 @@
 
                         @**<!-- labels -->**@
                         @if(!IssueLabel.findByProject(project).isEmpty){
-                        <dl class="labels-wrap">
-                            <dt>
-                                @Messages("label")
-                                <button type="button" class="ybtn ybtn-mini edit-button" style="margin-left:10px;"><i class="yobicon-edit vmiddle"></i></button>
-                            </dt>
-                            <dd>
-                            <dd style="padding:5px 10px; line-height:30px; @if(isAllowed(UserApp.currentUser(), issue.asResource(), Operation.UPDATE)){padding-left:0;}">
-                                @if(isAllowed(UserApp.currentUser(), issue.asResource(), Operation.UPDATE)) {
-                                    <select id="issueLabels" multiple="multiple" data-toggle="select2" data-format="issuelabel"
-                                            data-dropdown-css-class="issue-labels" data-container-css-class="issue-labels">
-                                        <option></option>
-                                        @IssueLabel.findByProject(project).groupBy(_.category).map {
-                                            case (category, labels) => {
-                                                <optgroup label="@category.name">
-                                                @labels.map { label =>
-                                                    <option value="@label.id" data-category="@category.id" @if(issue.labels.contains(label)){ selected }>
-                                                @label.name
-                                                </option>
-                                                }
-                                                </optgroup>
-                                            }
-                                        }
-                                    </select>
-                                } else {
-                                    @for(label <- issue.labels.toList.sortBy(r => (r.category.name, r.name))) {
-                                        <a href='@routes.IssueApp.issues(project.owner, project.name, issue.state.state(), "html", 1)&labelIds=@label.id' class="label issue-label active static" data-label-id="@label.id" data-color="@label.color" style="background:@label.color">@label.name</a>
-                                    }
-                                }
-                            </dd>
-                        </dl>
+                            @if(isAllowed(UserApp.currentUser(), issue.asResource(), Operation.UPDATE)){
+                                @partial_select_label(IssueLabel.findByProject(project), issue.getLabelIds)
+                            } else {
+                                @partial_show_selected_label(issue.labels.toList, routes.IssueApp.issues(project.owner, project.name, issue.state.state(), "html", 1).toString)
+                            }
                         }
                         @**<!-- // -->**@
                     </form>
@@ -281,74 +296,11 @@
             </div>
         </div>
 
-        <div class="board-footer board-actrow">
-            <div class="pull-left">
-                <div>
-                    @if(isAllowed(UserApp.currentUser(), issue.asResource(), Operation.WATCH)) {
-                        <button id="watch-button" type="button" class="ybtn @if(issue.getWatchers.contains(UserApp.currentUser())) {ybtn-watching}"
-                                data-toggle="button" data-watching="@if(issue.getWatchers.contains(UserApp.currentUser())){true}else{false}">
-                        @if(issue.getWatchers.contains(UserApp.currentUser())) {
-                            @Messages("project.unwatch")
-                        } else {
-                            @Messages("project.watch")
-                        }
-                        </button>
-                    }
 
-                    <div id="vote" class="vote-wrap">
-                        @if(isResourceCreatable(User.findByLoginId(session.get("loginId")), issue.asResource(), ResourceType.ISSUE_COMMENT)) {
-                            <a href="@urlToVote" class="ybtn @if(issue.isVotedBy(UserApp.currentUser)){ybtn-watching}" title="@getVoteButtonTitle"
-                               data-request-method="post" data-toggle="tooltip">
-                                <span class="heart"><i class="yobicon-hearts"></i></span>
-                                <span class="desc">@Messages("issue.vote")</span>
-                                @if(issue.voters.size > 0) {
-                                    <strong class="count">@issue.voters.size</strong>
-                                }
-                            </a>
-                        } else {
-                            <span class="ybtn ybtn-disabled" style="color:#777;" data-toggle="tooltip" title="@Messages("user.login.alert")" data-login="required">
-                                <span class="heart"><i class="yobicon-hearts"></i></span>
-                                <span class="desc">@Messages("issue.vote")</span>
-                                @if(issue.voters.size > 0) {
-                                    <strong class="count">@issue.voters.size</strong>
-                                }
-                            </span>
-                        }
-
-                        @if(issue.voters.size > 0){
-                            @partial_voters(issue, 3)
-                        }
-                    </div>
-                </div>
-            </div>
-
-            @if(issue.canBeDeleted) {
-                @if(isAllowed(UserApp.currentUser(), issue.asResource(), Operation.DELETE)) {
-                <a href="#deleteConfirm" data-toggle="modal" class="ybtn ybtn-danger">@Messages("button.delete")</a>
-                }
-            } else {
-            <button class="ybtn ybtn-disabled" data-toggle="tooltip" data-placement="top" title="@Messages("issue.can.not.be.deleted")">@Messages("button.delete")</button>
-            }
-
-            @if(isAllowed(UserApp.currentUser(), issue.asResource(), Operation.UPDATE)) {
-            <a href="@routes.IssueApp.editIssueForm(project.owner, project.name, issue.getNumber)" class="ybtn">@Messages("button.edit")</a>
-            }
+        <div class="board-footer">
+            <a href="@urlToIssues" class="ybtn pull-left" data-button="historyBack">@Messages("button.list")</a>
+            @help.keymap("issueDetail", project)
         </div>
-
-        @** Comment **@
-        <div id="comments" class="board-comment-wrap">
-            <div id="timeline">
-                <div class="timeline-list">
-                @partial_comments(project, issue)
-                </div>
-            </div>
-            @common.commentForm(issue.asResource(), ResourceType.ISSUE_COMMENT, routes.IssueApp.newComment(project.owner, project.name, issue.getNumber).toString())
-        </div>
-        @** // Comment **@
-
-        <a href="@urlToIssues" class="ybtn pull-left" data-button="historyBack">@Messages("button.list")</a>
-
-        @help.keymap("issueDetail", project)
     </div>
 
     <script type="text/x-jquery-tmpl" id="tplAttachedFile"><!--
@@ -383,22 +335,18 @@
 <script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.caret.min.js")"></script>
 <script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.atwho.js")"></script>
 <script type="text/javascript">
-    $(document).ready(function(){
+    $(function(){
         // yobi.issue.View
         $yobi.loadModule("issue.View", {
-            "sWatchUrl"     : "@routes.WatchApp.watch(issue.asResource.asParameter)",
-            "sUnwatchUrl"   : "@routes.WatchApp.unwatch(issue.asResource.asParameter)",
-            "sIssuesUrl"    : "@routes.IssueApp.massUpdate(project.owner, project.name)",
-            "sTimelineUrl"  : "@routes.IssueApp.timeline(project.owner, project.name, issue.getNumber)",
-            "sIssueId"      : "@issue.id",
-            "welMilestone"  : $("#milestone"),
-            "welAssignee"   : $("#assignee"),
-            "welIssueUpdateForm": $("#issueUpdateForm"),
-            "sIssueCheckBoxesSelector": "[type=checkbox][name=checked-issue]",
-            "sNextState"    : "@issue.nextState().toString.toLowerCase",
-            "sNextStateUrl" : "@routes.IssueApp.nextState(project.owner, project.name, issue.getNumber)",
-            "sCommentWithStateUrl": "@routes.IssueApp.newComment(project.owner, project.name, issue.getNumber)",
-            "welDueDate": $("#issueDueDate")
+            "issueId"  : "@issue.id",
+            "nextState": "@issue.nextState().toString.toLowerCase",
+            "urls"     : {
+                "watch"     : "@routes.WatchApp.watch(issue.asResource.asParameter)",
+                "unwatch"   : "@routes.WatchApp.unwatch(issue.asResource.asParameter)",
+                "timeline"  : "@routes.IssueApp.timeline(project.owner, project.name, issue.getNumber)",
+                "nextState" : "@routes.IssueApp.nextState(project.owner, project.name, issue.getNumber)",
+                "massUpdate": "@routes.IssueApp.massUpdate(project.owner, project.name)"
+            }
         });
 
         // yobi.ShortcutKey
@@ -412,9 +360,10 @@
             }
         });
 
+        // yobi.Mention
         yobi.Mention({
-            target:'textarea[id^=editor-]',
-            url : "@Html(routes.ProjectApp.mentionList(project.owner, project.name, issue.getNumber, issue.asResource().getType.resource()).toString())"
+            "target": 'textarea[id^=editor-]',
+            "url"   : "@Html(routes.ProjectApp.mentionList(project.owner, project.name, issue.getNumber, issue.asResource().getType.resource()).toString())"
         });
     });
 </script>
app/views/project/issuelabels.scala.html (Renamed from app/views/issue/labels.scala.html)
--- app/views/issue/labels.scala.html
+++ app/views/project/issuelabels.scala.html
@@ -23,10 +23,12 @@
 @import utils.TemplateHelper._
 @import utils.AccessControl._
 
-@projectLayout("label", project, utils.MenuType.ISSUE){
-  @projectMenu(project, utils.MenuType.ISSUE, "main-menu-only")
+@projectLayout("label", project, utils.MenuType.PROJECT_SETTING){
+  @projectMenu(project, utils.MenuType.PROJECT_SETTING, "")
   <div class="page-wrap-outer">
     <div class="project-page-wrap label-editor-wrap">
+      @partial_settingmenu(project)
+
       @if(isProjectResourceCreatable(UserApp.currentUser, project, ResourceType.ISSUE_LABEL)){
       <form id="frmNewLabel" action="@routes.IssueLabelApp.newLabel(project.owner, project.name)" method="post" class="new-label-wrap">
         <strong class="form-legend">@Messages("label.new")</strong>
@@ -56,13 +58,13 @@
       }
 
       <div id="labelsList" class="issue-label-list-wrap">
-        @partial_labels_list(project, labels)
+        @partial_issuelabels_list(project, labels)
       </div>
     </div>
   </div>
 
-  @partial_labels_editcategory()
-  @partial_labels_editlabel(project)
+  @partial_issuelabels_editcategory()
+  @partial_issuelabels_editlabel(project)
   @common.select2()
 
   <script type="text/javascript">
app/views/project/partial_issuelabels_editcategory.scala.html (Renamed from app/views/issue/partial_labels_editcategory.scala.html)
--- app/views/issue/partial_labels_editcategory.scala.html
+++ app/views/project/partial_issuelabels_editcategory.scala.html
No changes
app/views/project/partial_issuelabels_editlabel.scala.html (Renamed from app/views/issue/partial_labels_editlabel.scala.html)
--- app/views/issue/partial_labels_editlabel.scala.html
+++ app/views/project/partial_issuelabels_editlabel.scala.html
No changes
app/views/project/partial_issuelabels_list.scala.html (Renamed from app/views/issue/partial_labels_list.scala.html)
--- app/views/issue/partial_labels_list.scala.html
+++ app/views/project/partial_issuelabels_list.scala.html
@@ -40,7 +40,7 @@
 
   @labels.groupBy(_.category).map {
     case (category, labelsInCategory) => {
-      <div class="row-fluid list-item" data-category="@category.id" data-category-name="@category.name">
+      <div class="row-fluid list-item category-wrap" data-category="@category.id" data-category-name="@category.name">
         <div class="span3">
           <h5 class="right-txt mr20">
             <span class="category-name">
@@ -81,7 +81,8 @@
               <td class="actions">
                 @if(isAllowed(UserApp.currentUser, label.asResource(), Operation.DELETE)) {
                 <button type="button" class="ybtn ybtn-danger ybtn-small"
-                        data-category="@category.id" data-label-id="@label.id"
+                        data-category-name="@category.name"
+                        data-label-id="@label.id"
                         data-delete-uri="@routes.IssueLabelApp.delete(project.owner, project.name, label.id)">
                   @Messages("button.delete")
                 </button>
@@ -92,7 +93,7 @@
                         data-category-id="@category.id"
                         data-label-name="@label.name"
                         data-label-color="@label.color"
-                        data-label-update-uri="@routes.IssueLabelApp.update(project.owner, project.name, label.id)">
+                        data-update-uri="@routes.IssueLabelApp.update(project.owner, project.name, label.id)">
                   @Messages("button.edit")
                 </button>
                 }
app/views/project/partial_settingmenu.scala.html
--- app/views/project/partial_settingmenu.scala.html
+++ app/views/project/partial_settingmenu.scala.html
@@ -37,6 +37,7 @@
 <ul class="nav nav-tabs">
     @makeSubMenu(routes.ProjectApp.settingForm(project.owner, project.name),  Messages("project.setting"), 0)
     @makeSubMenu(routes.ProjectApp.members(project.owner, project.name),      Messages("project.member"), project.enrolledUsers.size)
+    @makeSubMenu(routes.IssueLabelApp.labels(project.owner, project.name),    Messages("issue.label"), 0)
+    @makeSubMenu(routes.ProjectApp.transferForm(project.owner, project.name), Messages("project.transfer"), 0)
     @makeSubMenu(routes.ProjectApp.deleteForm(project.owner, project.name),   Messages("project.delete"), 0)
-    @makeSubMenu(routes.ProjectApp.transferForm(project.owner, project.name),   Messages("project.transfer"), 0)
 </ul>
conf/messages
--- conf/messages
+++ conf/messages
@@ -231,6 +231,7 @@
 issue.event.referred.title = mentioned
 issue.event.unassigned = {0} set assignee to unassigned 
 issue.is.empty = No issue found
+issue.label = Issue Label
 issue.list.all = All issues
 issue.list.assignedToMe = Assigned to me
 issue.list.authoredByMe = Created by me
@@ -251,12 +252,12 @@
 issue.state.open = In progress
 issue.unvote.description = Click here if you no longer sympathize with this issue.
 issue.unwatch.start = You will no longer get notifications about this issue
-issue.update.assignee = Update assignee
+issue.update.assignee.id = Update assignee
 issue.update.attachLabel = Attach label
 issue.update.detachLabel = Detach label
-issue.update.duedate = Update due date
-issue.update.label = Update label
-issue.update.milestone = Update milestone
+issue.update.dueDate = Update due date
+issue.update.labelIds = Update label
+issue.update.milestone.id = Update milestone
 issue.update.state = Update status
 issue.vote = Agree
 issue.vote.description = Click here if you sympathize with this issue.
conf/messages.ja
--- conf/messages.ja
+++ conf/messages.ja
@@ -205,10 +205,11 @@
 issue.state.finished = 完了
 issue.state.open = オープン
 issue.unwatch.start = 今後このイシューに関するお知らせを受けません
-issue.update.assignee = 担当者変更
+issue.update.assignee.id = 担当者変更
 issue.update.attachLabel = ラベル追加
 issue.update.detachLabel = ラベル消去
-issue.update.milestone = マイルストーン変更
+issue.update.labelIds = ラベル変更
+issue.update.milestone.id = マイルストーン変更
 issue.update.state = 状態変更
 issue.watch.start = 今後このイシューに関するお知らせを受けることになります
 label = ラベル
conf/messages.ko
--- conf/messages.ko
+++ conf/messages.ko
@@ -164,6 +164,7 @@
 common.time.second = {0}초 전
 common.time.seconds = {0}초 전
 common.time.today = 오늘
+common.unselect = 선택안함
 emails.click.link = 이메일을 확인하려면 다음 링크를 클릭하세요.
 emails.main.email = 대표 이메일
 emails.main.email.descr = 대표 이메일로 설정한 이메일로 알림을 받거나 비밀번호 변경 요청을 받을 수 있습니다.
@@ -231,6 +232,7 @@
 issue.event.referred.title = 언급됨
 issue.event.unassigned = {0}님이 이 이슈의 담당자를 "없음"으로 설정하였습니다.
 issue.is.empty = 등록된 이슈가 없습니다.
+issue.label = 이슈 라벨
 issue.list.all = 전체 이슈
 issue.list.assignedToMe = 나에게 할당된 이슈
 issue.list.authoredByMe = 내가 작성한 이슈
@@ -251,12 +253,12 @@
 issue.state.open = 열림
 issue.unvote.description = 공감을 취소하려면 버튼을 누릅니다.
 issue.unwatch.start = 이제 이 이슈에 관한 알림을 받지 않습니다
-issue.update.assignee = 담당자 변경
+issue.update.assignee.id = 담당자 변경
 issue.update.attachLabel = 라벨 추가
 issue.update.detachLabel = 라벨 제거
-issue.update.duedate = 목표완료일 변경
-issue.update.label = 라벨 변경
-issue.update.milestone = 마일스톤 변경
+issue.update.dueDate = 목표완료일 변경
+issue.update.labelIds = 라벨 변경
+issue.update.milestone.id = 마일스톤 변경
 issue.update.state = 상태 변경
 issue.vote = 공감
 issue.vote.description = 이 이슈에 공감하기 위해 버튼을 누릅니다.
 
public/javascripts/common/yobi.Label.js (deleted)
--- public/javascripts/common/yobi.Label.js
@@ -1,382 +0,0 @@
-/**
- * Yobi, Project Hosting SW
- *
- * Copyright 2013 NAVER Corp.
- * http://yobi.io
- *
- * @Author Jihan Kim
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-/**
- * yobi.Label
- * 라벨 목록을 가져다 표현해주는 역할
- * (개별 삭제 링크 처리 포함)
- */
-yobi.Label = (function(htOptions){
-
-    var htVar = {};
-    var htElement = {};
-
-    /**
-     * initialize
-     * @param {Hash Table} htOptions
-     * @param {Boolean} htOptions.bEditable
-     * @param {String} htOptions.sTplLabel
-     * @param {String} htOptions.sTplControls
-     * @param {String} htOptions.sTplBtnLabelId
-     * @param {String} htOptions.sURLLabels
-     * @param {String} htOptions.sURLPost
-     */
-    function _init(htOptions){
-        htOptions = $.extend({"bEditable": false}, htOptions);
-        _initVar(htOptions);
-        _initElement(htOptions);
-        _initLabelEditor();
-        _attachEvent();
-    }
-
-    /**
-     *  initialize variable except element
-     *    htVar.sURLLabels = htOptions.sURLLabels;
-     *    htVar.sURLPost = htOptions.sURLPost;
-     *    htVar.bEditable = htOptions.bEditable;
-     *  @param {Hash Table} htOptions
-     */
-    function _initVar(htOptions){
-        // copy htOptions to htVar
-        for(key in htOptions){
-            htVar[key] = htOptions[key];
-        }
-
-        htVar.sURLLabel = htVar.sURLLabels.substr(0, htVar.sURLLabels.length-1);
-
-        htVar.sTplLabel = htOptions.sTplLabel || '<div class="control-group"><label class="control-label" data-category="${category}">${category}</label></div>';
-        htVar.sTplControls = htOptions.sTplControls || '<div class="controls label-group" data-category="${category}"></div>';
-        htVar.sTplBtnLabelId = htOptions.sTplBtnLabelId || '<span class="issue-label ${labelCSS} ${activeClass}" data-labelId="${labelId}">${labelName}${deleteButton}</span>';
-        htVar.sTplLabelItem = $('#labelListItem').text();
-        htVar.sTplLabelCategoryItem = $('#labelCatetoryItem').text();
-    }
-
-    /**
-     * initialize element variable
-     */
-    function _initElement(htOptions){
-        htElement.welContainer  = $(htOptions.welContainer || "fieldset.labels");
-        htElement.welForm = $(htOptions.welForm || 'form#issue-form,form.form-search,form#search');
-
-        // add label
-        htElement.welLabels = $('.labels');
-        htElement.welLabelEditor = $('.label-editor');
-
-        htElement.welBtnManageLabel = $(htOptions.welBtnManageLabel || "#manage-label-link");
-
-        // setting label list
-        htElement.welAttachLabels = $('#attach-label-list');
-        htElement.welDeleteLabels = $('#delete-label-list');
-    }
-
-    /**
-     * initialize event handler
-     */
-    function _attachEvent(){
-        htElement.welForm.submit(_onSubmitForm);
-        htElement.welBtnManageLabel.click(_clickBtnManageLabel);
-    }
-
-    function _onSubmitForm(){
-        // append labelIds to searchForm
-        if(htVar.bEditable === false){
-            _appendSelectedLabelIdsToForm();
-        }
-
-        return true;
-    }
-
-    /**
-     * @private
-     */
-    function _appendSelectedLabelIdsToForm(){
-        // clear former fields first
-        _clearLabelIdsOnForm();
-
-        var aValues = [];
-        var waSelectedLabels = $("fieldset.labels div[data-category] span.active[data-labelId]");
-
-        waSelectedLabels.each(function(i, elLabel){
-            aValues.push('<input type="hidden" name="labelIds" value="'+ $(elLabel).attr('data-labelId') + '">');
-        });
-
-        htElement.welForm.append(aValues);
-        waSelectedLabels = null;
-    }
-
-    /**
-     * @private
-     */
-    function _clearLabelIdsOnForm(){
-        $("input[name='labelIds']").each(function(i, elInput) {
-            $(elInput).remove();
-        });
-    }
-
-    function _clickBtnManageLabel() {
-        if(htVar.bEditable === false){
-            _appendSelectedLabelIdsToForm();
-        }
-
-        htVar.bEditable = !htVar.bEditable;
-        _initLabelEditor();
-    }
-
-    /**
-     * initialize Label Editor
-     */
-    function _initLabelEditor(){
-        htElement.welContainer.empty();
-
-        if(htVar.bEditable){
-            yobi.LabelEditor.appendTo(htElement.welContainer, {
-                "sURLPost" : htVar.sURLPost,
-                "fOnCreate": _onCreateNewLabel
-            });
-        }
-
-        _getLabels(htVar.fOnLoad);
-    }
-
-    /**
-     * @param {Object} oLabel
-     */
-    function _onCreateNewLabel(oLabel){
-        _addLabelIntoCategory(oLabel);
-        _setActiveLabel(oLabel.id, oLabel.color);
-        _addLabelForSettingGroup(oLabel);
-        $('input[name="labelName"]').val("");
-    }
-
-    function _addLabelForSettingGroup(oLabel) {
-        var sLabel;
-        var welAttachDivider = htElement.welAttachLabels.find('li[data-category="'+oLabel.category+'"].divider');
-        var welDeleteDivider = htElement.welDeleteLabels.find('li[data-category="'+oLabel.category+'"].divider');
-
-        if(welAttachDivider.length === 0) {
-            sLabel = $yobi.tmpl(htVar.sTplLabelCategoryItem, oLabel);
-            htElement.welAttachLabels.prepend(sLabel);
-            htElement.welDeleteLabels.prepend(sLabel);
-        } else {
-            sLabel = $yobi.tmpl(htVar.sTplLabelItem, oLabel);
-            welAttachDivider.before(sLabel);
-            welDeleteDivider.before(sLabel);
-        }
-    }
-
-    /**
-     * @param {Function} fCallback
-     */
-    function _getLabels(fCallback){
-        // send request
-        $.get(htVar.sURLLabels, function(oRes){
-            if(!(oRes instanceof Object)){
-                return;
-            }
-
-            // add label into category after sort
-            var aLabels = oRes.sort(function(a, b) {
-                return (a.category == b.category) ? (a.name > b.name) : (a.category > b.category);
-            });
-            $(aLabels).each(function(nIndex, oLabel){
-                _addLabelIntoCategory(oLabel);
-            });
-
-            // run callback function
-            if (typeof fCallback == "function") {
-                fCallback(this);
-            }
-
-            aLabels = null;
-        });
-    }
-
-    /**
-     * add label into category
-     * @param {Number} oLabel.id
-     * @param {String} oLabel.category
-     * @param {String} oLabel.name
-     * @param {String} oLabel.color
-     * @return {Wrapped Element}
-     */
-    function _addLabelIntoCategory(oLabel) {
-        // set Label Color
-        _setLabelColor(oLabel);
-
-        // label Id
-        var welBtnLabelId = $($yobi.tmpl(htVar.sTplBtnLabelId, {
-            "labelId"     : oLabel.id,
-            "labelName"   : oLabel.name,
-            "labelCSS"    : 'active-' + $yobi.getContrastColor(oLabel.color),
-            "activeClass" : _getActiveClass(parseInt(oLabel.id)),
-            "deleteButton": htVar.bEditable ? '<span class="delete">&times;</span>' : ''
-        }));
-
-        if(htVar.bEditable){
-            welBtnLabelId.addClass('active');
-        }
-        welBtnLabelId.click(_onClickLabel);
-
-        var welCategory = $('fieldset.labels div[data-category="' + oLabel.category + '"]');
-        if (welCategory.length > 0) {
-            welCategory.append(welBtnLabelId);
-            return welBtnLabelId;
-        }
-
-        var welLabel = $.tmpl(htVar.sTplLabel, {"category": oLabel.category});
-        var welControls = $.tmpl(htVar.sTplControls, {"category": oLabel.category});
-        welControls.append(welBtnLabelId); // Edit Button
-        welLabel.append(welControls); // Controls
-
-        if(htVar.bEditable){
-            yobi.LabelEditor.addCategory(oLabel.category);
-        }
-
-        // add label into category
-        if(htElement.welLabelEditor.length > 0) {
-            htElement.welLabelEditor.before(welLabel);
-        } else {
-            htElement.welLabels.prepend(welLabel);
-        }
-
-        return welBtnLabelId;
-    }
-
-    /**
-     * @param {Object} oLabel
-     */
-    function _setLabelColor(oLabel){
-        var sDefaultCSSTarget = '.issue-label[data-labelId="' + oLabel.id + '"]';
-        var sActiveCSSTarget = '.issue-label.active[data-labelId="' + oLabel.id + '"]';
-
-        var aDefaultCss = [];
-        var sDefaultCssSkel = 'box-shadow: inset 2px 0 0px ' + oLabel.color;
-        ["", "-moz-", "-webkit-"].forEach(function(sPrefix){
-            aDefaultCss.push(sPrefix + sDefaultCssSkel);
-        });
-        var sDefaultCss = aDefaultCss.join(";");
-        var sActiveCss = 'background-color: ' + oLabel.color + '; color:'+$yobi.getContrastColor(oLabel.color);
-
-        if(document.styleSheets[0].addRule) {
-            document.styleSheets[0].addRule(sActiveCSSTarget,sActiveCss);
-            document.styleSheets[0].addRule(sDefaultCSSTarget,sDefaultCss);
-        } else {
-            document.styleSheets[0].insertRule(sActiveCSSTarget+'{'+ sActiveCss +'}',0);
-            document.styleSheets[0].insertRule(sDefaultCSSTarget+'{'+ sDefaultCss +'}',0);
-        }
-    }
-
-    /**
-     * @param {Number} nLabelId
-     */
-    function _getActiveClass(nLabelId) {
-        if (htVar.aSelectedLabels && htVar.aSelectedLabels.indexOf(nLabelId) != -1) {
-            return 'active';
-        }
-        return '';
-    }
-
-    /**
-     * @param {Wrapped Event} weEvt
-     * @return {Boolean} false
-     */
-    function _onClickLabel(weEvt){
-        var welCurrent = $(weEvt.target); // SPAN .delete or .issue-label
-        var welLabel = welCurrent.attr("data-labelId") ? welCurrent : welCurrent.parent("[data-labelId]");
-        var sLabelId = welLabel.attr("data-labelId");
-
-        if(htVar.bEditable && welCurrent.hasClass("delete")){
-            if(confirm(Messages("label.confirm.delete")) === false){
-                return false;
-            }
-
-            return _requestDeleteLabel(sLabelId);
-        }
-
-        if(!htVar.bEditable){
-            welLabel.siblings().removeClass("active");
-            welLabel.toggleClass("active");
-        }
-
-        if (htVar.bRefresh) {
-            htElement.welForm.submit();
-        }
-
-        return false;
-    }
-
-
-    /**
-     * request to delete label
-     * @param sLabelId
-     * @private
-     */
-    function _requestDeleteLabel(sLabelId){
-        if(!sLabelId){
-            return false;
-        }
-
-        $.post(
-            htVar.sURLLabel + '/' + sLabelId + '/delete',
-            {"_method": "delete"}
-        ).done(function(){
-            _removeLabel(sLabelId);
-        });
-
-        return true;
-    }
-
-    /**
-     * remove label on list
-     * @param {String} sLabelId
-     */
-    function _removeLabel(sLabelId) {
-        var welEditorLabel = $('.issue-form-labels').find('[data-labelId=' + sLabelId + ']');
-        var welSettingLabel = htElement.welAttachLabels.find('li[data-value="'+sLabelId+'"]');
-
-        if(welEditorLabel.siblings().length ===0) {
-            var sCategory = welSettingLabel.data('category');
-            welEditorLabel.parents('div.control-group').remove();
-            htElement.welAttachLabels.find('li[data-category="'+sCategory+'"]').remove();
-            yobi.LabelEditor.removeCategory(sCategory);
-        } else {
-            welEditorLabel.remove();
-            welSettingLabel.remove();
-        }
-    }
-
-    /**
-     * @param {String} sId
-     */
-    function _setActiveLabel(sId){
-        $('.labels .issue-label[data-labelId="' + sId + '"]').addClass('active');
-    }
-
-    function _resetLabel(labelId) {
-        $(".labels .issue-label").removeClass("active");
-        _setActiveLabel(labelId);
-    }
-
-    return {
-        "init": _init,
-        "setActiveLabel": _setActiveLabel,
-        "resetLabel": _resetLabel
-    };
-})();
 
public/javascripts/common/yobi.LabelEditor.js (deleted)
--- public/javascripts/common/yobi.LabelEditor.js
@@ -1,326 +0,0 @@
-/**
- * Yobi, Project Hosting SW
- *
- * Copyright 2013 NAVER Corp.
- * http://yobi.io
- *
- * @Author Jihan Kim
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-/**
- * yobi.LabelEditor
- * 새 라벨 추가를 위한 에디터 인터페이스
- */
-yobi.LabelEditor = (function(welContainer, htOptions){
-
-    var htVar = {};
-    var htElement = {};
-
-    /**
-     * initialize
-     * @param {Wrapped Element} welContainer Container element to append label editor
-     * @param {Hash Table} htOptions
-     */
-    function _init(welContainer, htOptions){
-        _initVar(htOptions);
-        _initElement(welContainer);
-        _attachEvent();
-    }
-
-    /**
-     * initialize variables
-     * @param {Hash Table} htOptions
-     * @param {Function} htOptions.fOnCreate
-     * @param {String}   htOptions.sURLPost
-     * @param {String}   htOptions.sTplEditor
-     * @param {String}   htOptions.sTplBtnColor
-     * @param {Array}    htOptions.aColors
-     */
-    function _initVar(htOptions){
-        htVar.sURLPost = htOptions.sURLPost;
-        htVar.fOnCreate = htOptions.fOnCreate;
-
-        htVar.aColors = htOptions.aColors || ['#da5454','#f86ca0','#ff9e9d','#ffcc33','#f8c86c','#ff9933','#99ca3c','#3fb8af','#22b4b9','#6ca6f8','#4d68b1','#9966cc'];
-        htVar.sTplEditor = htOptions.sTplEditor || $("#tplYobiLabelEditor").text();
-        htVar.sTplBtnColor = htOptions.sTplBtnColor || '<button type="button" class="issue-label issueColor nbtn square" style="background-color:${color}">';
-    }
-
-    /**
-     * initialize elements
-     * @param {Wrapped Element} welContainer
-     */
-    function _initElement(welContainer){
-        htElement.welContainer = $(welContainer);
-        htElement.welEditor = _getLabelEditor();
-        htElement.welContainer.append(htElement.welEditor);
-
-        htElement.welWrap = $("#custom-label");
-        htElement.welColors = htElement.welWrap.find("div.colors");
-        _makeColorTable(); // 색상표 생성
-
-        htElement.waBtnCustomColor = htElement.welWrap.find("button.issueColor");
-        htElement.welCustomLabelColor = htElement.welWrap.find("input[name=labelColor]"); // $('#custom-label-color');
-        htElement.welCustomLabelName =  htElement.welWrap.find("input[name=labelName]");  // $('#custom-label-name');
-        htElement.welCustomLabelCategory = htElement.welWrap.find("input[name=labelCategory]"); // $('#custom-label-category');
-        htElement.welCustomLabelCategory.typeahead();
-        htElement.welBtnCustomLabelSubmit  = htElement.welWrap.find("button.labelSubmit"); //$('#custom-label-submit');
-
-        // Focus to the category input area.
-        htElement.welCustomLabelCategory.focus();
-    }
-
-    /**
-     * @returns {Boolean} false on enter key has pressed
-     */
-    function _preventDefaultWhenEnterPressed(weEvt) {
-        return !((weEvt.keyCode || weEvt.which) === 13);
-    }
-
-    /**
-     * @private false when enter key pressed
-     */
-    function _preventSubmitAndMoveWhenEnterPressed(eEvt, welTarget){
-        var code = eEvt.keyCode || eEvt.which;
-        if(code === 13){
-            welTarget.focus();
-            eEvt.preventDefault();
-        }
-    }
-
-    /**
-     * attach events
-     */
-    function _attachEvent(){
-        htElement.waBtnCustomColor.click(_onClickBtnCustomColor);
-        htElement.welBtnCustomLabelSubmit.click(_onClickBtnSubmitCustom);
-
-        htElement.welCustomLabelCategory
-            .keypress(_preventDefaultWhenEnterPressed);
-        htElement.welCustomLabelName
-            .focus(_onFocusLabelName)
-            .keypress(_preventDefaultWhenEnterPressed)
-            .keyup(function(weEvt) {
-                if((weEvt.keyCode || weEvt.which) === 13){
-                    htElement.welCustomLabelColor.focus();
-                    weEvt.preventDefault();
-                    return false;
-                }
-            });
-        htElement.welCustomLabelColor
-            .keypress(function(weEvt){
-                if((weEvt.keyCode || weEvt.which) === 13){
-                    _addCustomLabel();
-                    weEvt.preventDefault();
-                    return false;
-                }
-            })
-            .keyup(function(weEvt){
-                _preventSubmitAndMoveWhenEnterPressed(weEvt, htElement.welCustomLabelName);
-            });
-        htElement.welCustomLabelColor.keyup(_onKeyupInputColorCustom);
-    }
-
-    function _onFocusLabelName(){
-        var sCategory = htElement.welCustomLabelCategory.val();
-        var sColor = htElement.welCustomLabelColor.val();
-
-        if(!sCategory.trim()){
-            return;
-        }
-
-        var welFirstItemInCategory = $('.label-group[data-category="' + sCategory + '"] > .issue-label:first');
-
-        if(welFirstItemInCategory.length > 0 && !sColor.trim()){
-            var sColor = new RGBColor(welFirstItemInCategory.css("background-color")).toHex();
-            _updateSelectedColor(sColor);
-            htElement.welCustomLabelColor.val(sColor);
-        }
-    }
-
-    /**
-     * Get label Editor
-     * @return {Wrapped Element}
-     */
-    function _getLabelEditor(){
-        // label editor HTML
-        var welEditor = $yobi.tmpl(htVar.sTplEditor, {
-            "labelAdd"        : Messages("label.add"),
-            "labelNew"        : Messages("label.new"),
-            "labelName"       : Messages("label.name"),
-            "labelCategory"   : Messages('label.category'),
-            "labelCustomColor": Messages("label.customColor")
-        });
-
-        return welEditor;
-    }
-
-    function _makeColorTable(){
-        var aColorBtns = [];
-        htVar.aColors.forEach(function(sColor){
-            aColorBtns.push($yobi.tmpl(htVar.sTplBtnColor, {"color": sColor}));
-        });
-        htElement.welColors.prepend(aColorBtns);
-        aColorBtns = null;
-    }
-
-    function _onClickBtnSubmitCustom(){
-        _addCustomLabel();
-        htElement.welCustomLabelName.focus();
-    }
-
-    /**
-     * add custom label
-     */
-    function _addCustomLabel(){
-        var htData = {
-            "name"    : htElement.welCustomLabelName.val(),
-            "color"   : htElement.welCustomLabelColor.val(),
-            "category": htElement.welCustomLabelCategory.val()
-        };
-
-        if(htData.name.length === 0 || htData.color.length === 0 || htData.category.length === 0){
-            $yobi.alert(Messages("label.error.empty"));
-            return false;
-        }
-
-        var sColor = htElement.welCustomLabelColor.val();
-        var oColor = new RGBColor(sColor);
-
-        if(!oColor.ok){
-            $yobi.alert(Messages("label.error.color", sColor));
-            return false;
-        }
-
-        htElement.welCustomLabelColor.val(oColor.toHex());
-
-        // send request
-        $yobi.sendForm({
-            "sURL"     : htVar.sURLPost,
-            "htData"   : htData,
-            "htOptForm": {"enctype": "multipart/form-data"},
-            "fOnLoad"  : function(oRes){
-                // label.id, label.category, label.name, label.color
-                if (!(oRes instanceof Object)) {
-                    var sMessage = Messages("label.error.creationFailed");
-
-                    if($(".labels .issue-label:contains('" + htData.name + "')").length > 0){
-                        sMessage = Messages("label.error.duplicated");
-                    }
-
-                    $yobi.alert(sMessage);
-                    return;
-                }
-
-                htElement.welCustomLabelName.val("");
-                _addCategoryTypeahead(oRes.category);
-