Jihan Kim 2014-04-09
Unify markdown editor form
@a4f434045c8324c8eeeeaf1ecccfbca1a594d0f7
app/assets/stylesheets/less/_page.less
--- app/assets/stylesheets/less/_page.less
+++ app/assets/stylesheets/less/_page.less
@@ -2895,8 +2895,8 @@
     overflow-x:auto;
     margin:0px;
     vertical-align : middle;
-    border:3px solid #ddd;
-    padding:10px;
+    border:2px solid #ccc;
+    padding:4px 6px;
     .border-radius(0 0 3px 3px);
 
 }
@@ -3776,7 +3776,6 @@
     .write-comment-box {
         font-family:@base-font-family;
         padding:5px;
-        background-color: #f5f5f5;
     }
 
     /** view **/
@@ -5034,7 +5033,6 @@
                                 form { margin:0;}
 
                                 .write-comment-box {
-                                    background-color: #f5f5f5;
                                     padding:15px 0 15px 50px;
                                     margin:0;
 
@@ -5180,7 +5178,7 @@
 
 .review-form {
     position:absolute; display: none;
-    z-index:99; top:0px; left:0px; right:0px;
+    z-index:1000; top:0px; left:0px; right:0px;
     padding: 15px 15px 10px;
     background: #fff;
     border:1px solid #ccc;
app/assets/stylesheets/less/_yobiUI.less
--- app/assets/stylesheets/less/_yobiUI.less
+++ app/assets/stylesheets/less/_yobiUI.less
@@ -461,6 +461,11 @@
             font-weight:bold;
         }
     }
+
+    &.small {
+        height:29px;
+        li a { padding:4px 15px; }
+    }
 }
 
 /** alert **/
@@ -547,7 +552,7 @@
 
 .yobiSpinner {
     display: none;
-    position: fixed; z-index: 999;
+    position: fixed; z-index: 99999;
     top: 0; bottom: 0; left: 0; right: 0;
 
     .spinContainer {
app/utils/TemplateHelper.scala
--- app/utils/TemplateHelper.scala
+++ app/utils/TemplateHelper.scala
@@ -44,6 +44,14 @@
     baseUrl + prefix + query.dropRight(1)
   }
 
+  def buildAttrString(attrMap: java.util.Map[String, String]): String = {
+    var attr = ""
+    attrMap.map {
+      v => attr += v._1 + "=" + v._2 + " "
+    }
+    attr.dropRight(1)
+  }
+
   def agoString(duration: org.joda.time.Duration) = {
     if (duration != null){
       val sec = duration.getMillis / DateTimeConstants.MILLIS_PER_SECOND
app/views/board/create.scala.html
--- app/views/board/create.scala.html
+++ app/views/board/create.scala.html
@@ -1,5 +1,6 @@
 @(title:String, form:Form[Posting], project:Project, isAllowedToNotice:Boolean)
 
+@import scala.collection.Map
 @import utils.TemplateHelper._
 @import models.enumeration._
 @implicitField = @{ helper.FieldConstructor(simpleForm) }
@@ -28,18 +29,7 @@
     			</dd>
 
                 <dd style="position: relative;">
-                    <div id="mode-select">
-                        <input type="radio" name="edit-mode" id="edit-mode" value="edit" checked="checked" class="radio-btn">
-                        <label for="edit-mode" style="margin-right:3px;">Edit</label>
-                        <input type="radio" name="edit-mode" id="preview-mode" value="preview" class="radio-btn">
-                        <label for="preview-mode">Preview</label>
-                    </div>
-                    <a href="#" class="go-zen"><i class="s s--zen" >Edit in Zen Mode</i></a>
-                    @help.markdown()
-    				 <div class="textarea-box">
-                        <textarea id="body" name="body" class="zen-mode content" markdown="true" tabindex="2" data-label="@Messages("post.error.emptyBody")"></textarea>
-                    </div>
-
+                    @common.editor("body", "", Map("tabindex"->"2"))
     			</dd>
 
     			@if(isAllowedToNotice){
app/views/board/edit.scala.html
--- app/views/board/edit.scala.html
+++ app/views/board/edit.scala.html
@@ -1,5 +1,6 @@
 @(title:String, form:Form[Posting], posting:Posting, number:Long,  project:Project, isAllowedToNotice:Boolean)
 
+@import scala.collection.Map
 @import utils.TemplateHelper._
 @import models.enumeration.ResourceType
 @implicitField = @{ helper.FieldConstructor(simpleForm) }
@@ -28,17 +29,7 @@
     			</dd>
 
     		    <dd style="position: relative;">
-                    <div id="mode-select">
-                        <input type="radio" name="edit-mode" id="edit-mode" value="edit" checked="checked" class="radio-btn">
-                        <label for="edit-mode" style="margin-right:3px;">Edit</label>
-                        <input type="radio" name="edit-mode" id="preview-mode" value="preview" class="radio-btn">
-                        <label for="preview-mode">Preview</label>
-                    </div>
-                    <a href="#" class="go-zen" ><i class="s s--zen">Edit in Zen Mode</i></a>
-                    @help.markdown()
-                    <div class="textarea-box">
-                        <textarea id="body" name="body" class="zen-mode content" markdown="true" tabindex="2" data-label="@Messages("post.error.emptyBody")">@posting.body</textarea>
-                    </div>
+                    @common.editor("body", posting.body, Map("tabindex"->"2"))
     			</dd>
 
     			@if(isAllowedToNotice){
 
app/views/board/partial_comments.scala.html (added)
+++ app/views/board/partial_comments.scala.html
@@ -0,0 +1,71 @@
+@**
+/**
+* Yobi, Project Hosting SW
+*
+* Copyright 2013 NAVER Corp.
+* http://yobi.io
+*
+* 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.
+*/
+**@
+
+@(project:Project, post:Posting)
+
+@import utils.TemplateHelper._
+@import utils.AccessControl._
+@import utils.JodaDateUtil
+
+@isAuthorComment(commentId: String) = @{
+    if(commentId == UserApp.currentUser().loginId) {"author"}
+}
+
+
+<div class="comment-header"><i class="yobicon-comments"></i> <strong>@Messages("common.comment")</strong> <strong class="num">@post.comments.size</strong></div>
+<hr class="nm" />
+
+<ul class="comments">
+@for(comment <-post.comments){
+    <li class="comment @isAuthorComment(comment.authorLoginId)" id="comment-@comment.id">
+        <div class="comment-avatar">
+            <a href="@routes.UserApp.userInfo(comment.authorLoginId)" class="avatar-wrap" data-toggle="tooltip" data-placement="top" title="@comment.authorName">
+                <img src="@User.findByLoginId(comment.authorLoginId).avatarUrl" width="32" height="32" alt="@comment.authorLoginId">
+            </a>
+        </div>
+        <div class="media-body">
+            <div class="meta-info">
+                <span class="comment_author pull-left">
+                    <a href="@routes.UserApp.userInfo(comment.authorLoginId)" data-toggle="tooltip" data-placement="top" title="@comment.authorName">
+                        <strong>@comment.authorLoginId </strong>
+                    </a>
+                </span>
+                <a href="#comment-@comment.id" class="ago" title="@JodaDateUtil.getDateString(comment.createdDate)">@utils.TemplateHelper.agoString(comment.ago())</a>
+                <span class="act-row pull-right">
+                    @if(isAllowed(UserApp.currentUser(), comment.asResource(), Operation.UPDATE)) {
+                        <button type="button" class="btn-transparent mr10" data-toggle="comment-edit" data-comment-id="@comment.id" title="@Messages("common.comment.edit")"><i class="yobicon-edit-2"></i></button>
+                    }
+                    @if(isAllowed(UserApp.currentUser(), comment.asResource(), Operation.DELETE)) {
+                        <button type="button" class="btn-transparent" data-toggle="comment-delete" data-request-uri="@routes.BoardApp.deleteComment(project.owner, project.name, post.getNumber, comment.id)" title="@Messages("common.comment.delete")"><i class="yobicon-trash"></i></button>
+                    }
+                </span>
+            </div>
+
+            @common.commentUpdateForm(comment.id, routes.BoardApp.newComment(project.owner, project.name, post.getNumber).toString(), comment.contents)
+
+            <div id="comment-body-@comment.id">
+                <div class="comment-body markdown-wrap markdown-before" markdown="true">@comment.contents</div>
+                <div class="attachments pull-right" data-resourceType="@ResourceType.NONISSUE_COMMENT" data-resourceId="@comment.id"></div>
+            </div>
+        </div>
+    </li>
+}
+</ul>
app/views/board/view.scala.html
--- app/views/board/view.scala.html
+++ app/views/board/view.scala.html
@@ -8,10 +8,6 @@
 
 @urlToPostings = @{ urlToList(request.getHeader("Referer"), routes.BoardApp.posts(project.owner, project.name).toString()) }
 
-@isAuthorComment(commentId: String) = @{
-    if(commentId == UserApp.currentUser().loginId) {"author"}
-}
-
 @projectLayout(post.title, project, utils.MenuType.BOARD){
 @projectMenu(project, utils.MenuType.BOARD, "main-menu-only")
 <div class="page-wrap-outer">
@@ -68,43 +64,7 @@
 
     	@** Comment **@
     	<div id="comments" class="board-comment-wrap">
-    		<div class="comment-header"><i class="yobicon-comments"></i> <strong>@Messages("common.comment")</strong> <strong class="num">@post.comments.size</strong></div>
-    	    <hr class="nm" />
-
-    	    <ul class="comments">
-    	    @for(comment <-post.comments){
-    			<li class="comment @isAuthorComment(comment.authorLoginId)" id="comment-@comment.id">
-                    <div class="comment-avatar">
-                        <a href="@routes.UserApp.userInfo(comment.authorLoginId)" class="avatar-wrap" data-toggle="tooltip" data-placement="top" title="@comment.authorName">
-                            <img src="@User.findByLoginId(comment.authorLoginId).avatarUrl" width="32" height="32" alt="@comment.authorLoginId">
-                        </a>
-                    </div>
-                    @common.commentUpdateForm(project, ResourceType.NONISSUE_COMMENT, comment.id, routes.BoardApp.newComment(project.owner, project.name, post.getNumber).toString(), comment.contents)
-                    <div class="media-body" id="comment-body-@comment.id">
-                        <div class="meta-info">
-                            <span class="comment_author pull-left">
-                                <a href="@routes.UserApp.userInfo(comment.authorLoginId)" data-toggle="tooltip" data-placement="top" title="@comment.authorName">
-                                    <strong>@comment.authorLoginId </strong>
-                                </a>
-                            </span>
-                            <a href="#comment-@comment.id" class="ago" title="@JodaDateUtil.getDateString(comment.createdDate)">@utils.TemplateHelper.agoString(comment.ago())</a>
-                            <span class="act-row pull-right">
-                                @if(isAllowed(UserApp.currentUser(), comment.asResource(), Operation.UPDATE)) {
-                                    <button type="button" class="btn-transparent mr10" data-toggle="comment-edit" data-comment-editform-id="comment-editform-@comment.id" title="@Messages("common.comment.edit")"><i class="yobicon-edit-2"></i></button>
-                                }
-                                @if(isAllowed(UserApp.currentUser(), comment.asResource(), Operation.DELETE)) {
-                                    <button type="button" class="btn-transparent" data-toggle="comment-delete" data-request-uri="@routes.BoardApp.deleteComment(project.owner, project.name, post.getNumber, comment.id)" title="@Messages("common.comment.delete")"><i class="yobicon-trash"></i></button>
-                                }
-                            </span>
-                        </div>
-
-                        <div class="comment-body markdown-wrap markdown-before" markdown="true">@comment.contents</div>
-
-                        <div class="attachments pull-right" data-resourceType="@ResourceType.NONISSUE_COMMENT" data-resourceId="@comment.id"></div>
-                    </div>
-                </li>
-    	    }
-    		</ul>
+            @partial_comments(project, post)
 
             @common.commentForm(post.asResource(), ResourceType.NONISSUE_COMMENT, routes.BoardApp.newComment(project.owner, project.name, post.getNumber).toString())
     	</div>
app/views/common/commentForm.scala.html
--- app/views/common/commentForm.scala.html
+++ app/views/common/commentForm.scala.html
@@ -1,34 +1,22 @@
 @(container: models.resource.Resource, resourceType:ResourceType, action:String)
 
+@import scala.collection.Map
 @import models.enumeration.ResourceType
 @import utils.AccessControl._
 
 @if(isResourceCreatable(User.findByLoginId(session.get("loginId")), container, resourceType)){
 
     <form id="comment-form" action="@action" method="post" enctype="multipart/form-data">
-        <div class="write-comment-box">
+        <div class="write-comment-box clearbg">
+            @common.editor("contents", "", Map("id"->"comment-editor"))
+
+            @** fileUploader **@
+            @if(!UserApp.currentUser.isAnonymous){
+                @common.fileUploader(resourceType, null)
+            }
+            @** end of fileUploader **@
+
             <div class="write-comment-wrap">
-                <div id="mode-select" data-toggle="buttons">
-                    <ul class="nav nav-tabs">
-                        <li class="active">
-                            <input type="radio" name="edit-mode" id="edit-mode" value="edit" checked="checked" class="radio-btn">
-                            <a href="#" data-toggle="tab" onclick="$('#edit-mode').trigger('click')">Edit</a>
-                        </li>
-                        <li>
-                            <input type="radio" name="edit-mode" id="preview-mode" value="preview" class="radio-btn">
-                            <a href="#" onclick="$('#preview-mode').trigger('click')" data-toggle="tab">Preview</a>
-                        </li>
-                    </ul>
-                </div>
-                @help.markdown()
-                <div class="textarea-box">
-                    <textarea id="comment-editor" name="contents" class="comment" markdown="true"></textarea>
-                </div>
-                @** fileUploader **@
-                @if(!UserApp.currentUser.isAnonymous) {
-                    @common.fileUploader(resourceType, null)
-                }
-                @** end of fileUploader **@
                 <div class="right-txt">
                     <button type="button" class="ybtn hidden" id="dynamic-comment-btn"></button>
                     <button type="submit" class="ybtn ybtn-success">@Messages("button.comment.new")</button>
app/views/common/commentUpdateForm.scala.html
--- app/views/common/commentUpdateForm.scala.html
+++ app/views/common/commentUpdateForm.scala.html
@@ -1,32 +1,18 @@
-@(project:Project, resourceType:ResourceType, resourceId:Long, action:String, contents:String)
+@(commentId:Long, action:String, contents:String)
 
-@import models.enumeration.ResourceType
-    <div id="comment-editform-@resourceId" class="comment-update-form">
-    <form id="comment-update-form" action="@action" method="post">
+<div id="comment-editform-@commentId" class="comment-update-form">
+    <form action="@action" method="post">
+        <input type="hidden" name="id" value="@commentId">
+
         <div class="write-comment-box">
             <div class="write-comment-wrap">
-                <div id="mode-select" data-toggle="buttons">
-                    <ul class="nav nav-tabs">
-                    <li class="active">
-                        <input type="radio" name="edit-mode" id="edit-mode-@resourceId" value="edit" checked="checked" class="radio-btn">
-                        <a href="#" data-toggle="tab" onclick="$('#edit-mode-@resourceId').trigger('click')">Edit</a>
-                    </li>
-                    <li>
-                        <input type="radio" name="edit-mode" id="preview-mode-@resourceId" value="preview" class="radio-btn">
-                        <a href="#" onclick="$('#preview-mode-@resourceId').trigger('click')" data-toggle="tab">Preview</a>
-                    </li>
-                    </ul>
-                </div>
-                @help.markdown()
-                <input type="hidden" name="id" value="@resourceId">
-                <div class="textarea-box">
-                    <textarea id="comment-editor" name="contents" class="comment comment-editor" markdown="true">@contents</textarea>
-                </div>
+                @common.editor("contents", contents)
+
                 <div class="right-txt comment-update-button">
-                    <button type="button" class="ybtn ybtn-cancel" data-comment-body-id="comment-body-@resourceId">@Messages("button.cancel")</button>
+                    <button type="button" class="ybtn ybtn-cancel" data-comment-id="@commentId">@Messages("button.cancel")</button>
                     <button type="submit" class="ybtn ybtn-info">@Messages("button.save")</button>
                 </div>
             </div>
         </div>
     </form>
-    </div>
+</div>
 
app/views/common/editor.scala.html (added)
+++ app/views/common/editor.scala.html
@@ -0,0 +1,52 @@
+@**
+* 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.
+**@
+@(editorName:String = "contents", textValue:String="", textareaAttr:Map[String,String]=null)
+
+@import org.apache.commons.lang3.RandomStringUtils._
+@import utils.TemplateHelper._
+
+@defining(randomAlphabetic(8)){ wrapId =>
+<div data-toggle="markdown-editor">
+    <ul class="nav nav-tabs nm small">
+        <li class="active">
+            <a href="#edit-@wrapId" data-toggle="tab" data-mode="edit">@Messages("common.editor.edit")</a>
+        </li>
+        <li>
+            <a href="#preview-@wrapId" data-toggle="tab" data-mode="preview">@Messages("common.editor.preview")</a>
+        </li>
+    </ul>
+
+    <div class="tab-content" style="position:relative;">
+        @help.markdown()
+
+        <div id="edit-@wrapId" class="tab-pane active">
+            <div class="textarea-box">
+                <textarea name="@editorName" class="content comment nm" markdown="true"
+                    @if(textareaAttr != null){ @buildAttrString(textareaAttr) }>@textValue</textarea>
+            </div>
+        </div>
+
+        <div id="preview-@wrapId" class="tab-pane">
+            <div class="markdown-preview markdown-wrap"></div>
+        </div>
+    </div>
+</div>
+}
app/views/common/fileUploader.scala.html
--- app/views/common/fileUploader.scala.html
+++ app/views/common/fileUploader.scala.html
@@ -1,23 +1,6 @@
-@(resType:ResourceType, resId:Long)
+@(resourceType:ResourceType, resourceId:Long)
 
-<div id="upload" class="upload-wrap content-footer" data-resourceType="@resType" @if(resId != null){data-resourceId="@resId"}>
-    <div class="attach-wrap">
-        <span class="help help-droppable">@Messages("common.attach.drophere")</span>
-        <div class="btn-wrap">
-             <div class="nbtn medium white fake-file-wrap">
-                 <i class="yobicon-upload"></i> @Messages("button.upload")<!--
-             --> <input type="file" class="file" name="filePath" multiple="multiple">
-             </div>
-        </div>
-        <span class="plain">@Messages("common.attach.clickbutton")</span>
-        <span class="help help-pastable">@Messages("common.attach.pastehere")</span>
-    </div>
-
-    <ul class="attached-files unstyled"></ul>
-    <p class="right-txt help">
-        <i class="yobicon-supportrequest"></i> @Messages("common.attach.attachIfYouSave")
-    </p>
-</div>
+@common.uploadForm(resourceType, resourceId, "upload")
 
 <script type="text/x-jquery-tmpl" id="tplAttachedFile">
     <li class="attached-file" data-id="${fileId}" data-name="${fileName}" data-href="${fileHref}" data-mime="${mimeType}" data-size="${fileSize}">
app/views/common/markdown.scala.html
--- app/views/common/markdown.scala.html
+++ app/views/common/markdown.scala.html
@@ -1,7 +1,5 @@
 @(project: Project = null)
 
-@import utils.TemplateHelper._
-
 <link rel="stylesheet" type="text/css" href="@routes.Assets.at("javascripts/lib/highlight/styles/default.css")" />
 <script type="text/javascript" src="@routes.Assets.at("javascripts/lib/highlight/highlight.pack.js")"></script>
 <script type="text/javascript" src="@routes.Assets.at("javascripts/lib/marked.js")"></script>
@@ -9,7 +7,6 @@
 <script type="text/javascript">
 $(document).ready(function(){
     var htOptions = {
-        "sTplSwitch": $("#tplMarkdownPreview").text(),
         "bBreaks": ($('[markdown]').hasClass('readme-body')) ? false : true
     };
 
@@ -21,11 +18,4 @@
     // Reusable markdown renderer
     yobi.Markdown.init(htOptions);
 });
-</script>
-
-<script type="text/x-jquery-tmpl" id="tplMarkdownPreview">
-<input type="radio" name="edit-mode" value="edit" checked="checked" class="radio-btn" />
-<label for="edit-mode" style="margin-right:3px;">Edit</label>
-<input type="radio" name="edit-mode" value="preview" class="radio-btn" />
-<label for="preview-mode">Preview</label>
 </script>
app/views/common/reviewForm.scala.html
--- app/views/common/reviewForm.scala.html
+++ app/views/common/reviewForm.scala.html
@@ -38,31 +38,11 @@
         </div>
         <div class="write-comment-box nm">
             <div class="write-comment-wrap">
-                @help.markdown()
-                <div class="textarea-box">
-                    <textarea name="contents" class="text comment" markdown="true"></textarea>
-                </div>
+                @common.editor("contents")
 
                 @** fileUploader **@
                 @if(!UserApp.currentUser.isAnonymous){
-                <div class="upload-wrap content-footer" data-resourceType="@resourceType">
-                    <div class="attach-wrap">
-                        <span class="help help-droppable">@Messages("common.attach.drophere")</span>
-                        <div class="btn-wrap">
-                             <div class="nbtn medium white fake-file-wrap">
-                                 <i class="yobicon-upload"></i> @Messages("button.upload")<!--
-                             --> <input type="file" class="file" name="filePath" multiple="multiple">
-                             </div>
-                        </div>
-                        <span class="plain">@Messages("common.attach.clickbutton")</span>
-                        <span class="help help-pastable">@Messages("common.attach.pastehere")</span>
-                    </div>
-
-                    <ul class="attached-files unstyled"></ul>
-                    <p class="right-txt help">
-                        <i class="yobicon-supportrequest"></i> @Messages("common.attach.attachIfYouSave")
-                    </p>
-                </div>
+                    @common.uploadForm(resourceType)
                 }
                 @** end of fileUploader **@
 
 
app/views/common/uploadForm.scala.html (added)
+++ app/views/common/uploadForm.scala.html
@@ -0,0 +1,44 @@
+@**
+* 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.
+**@
+@(resourceType:ResourceType, resourceId:Long=null, formId:String=null)
+<div
+    class="upload-wrap content-footer"
+    data-resource-type="@resourceType"
+    @if(resourceId != null){data-resource-id="@resourceId"}
+    @if(formId != null){id="@formId"}
+>
+    <div class="attach-wrap">
+        <span class="help help-droppable">@Messages("common.attach.drophere")</span>
+        <div class="btn-wrap">
+            <div class="nbtn medium white fake-file-wrap">
+                <i class="yobicon-upload"></i> @Messages("button.upload")
+                <input type="file" class="file" name="filePath" multiple="multiple">
+            </div>
+        </div>
+        <span class="plain">@Messages("common.attach.clickbutton")</span>
+        <span class="help help-pastable">@Messages("common.attach.pastehere")</span>
+    </div>
+
+    <ul class="attached-files unstyled"></ul>
+    <p class="right-txt help">
+        <i class="yobicon-supportrequest"></i> @Messages("common.attach.attachIfYouSave")
+    </p>
+</div>
app/views/git/edit.scala.html
--- app/views/git/edit.scala.html
+++ app/views/git/edit.scala.html
@@ -1,5 +1,6 @@
 @(title:String, form: Form[PullRequest], project: Project, fromBranches: List[playRepository.GitBranch], toBranches: List[playRepository.GitBranch], pull: PullRequest)
 
+@import scala.collection.Map
 @import utils.TemplateHelper._
 @import utils.TemplateHelper.Branches._
 @implicitField = @{ helper.FieldConstructor(simpleForm) }
@@ -96,17 +97,7 @@
 
                 <input type="text" name="title" id="title" placeholder="" maxlength="maxlength" tabindex="1" class="text" value="@pull.title">
                 <div style="position: relative;">
-                    <div id="mode-select">
-                        <input type="radio" name="edit-mode" id="edit-mode" value="edit" checked="checked" class="radio-btn">
-                        <label for="edit-mode" style="margin-right:3px;">Edit</label>
-                        <input type="radio" name="edit-mode" id="preview-mode" value="preview" class="radio-btn">
-                        <label for="preview-mode">Preview</label>
-                    </div>
-                    <a href="#" class="go-zen"><i class="s s--zen">Edit in Zen Mode</i></a>
-                    @help.markdown()
-                    <div class="textarea-box">
-                        <textarea id="body" name="body" markdown="true" class="zen-mode content" tabindex="2" >@pull.body</textarea>
-                    </div>
+                    @common.editor("body", pull.body, Map("tabindex"->"2"))
                 </div>
 
                 @common.fileUploader(ResourceType.PULL_REQUEST, pull.id)
app/views/git/partial_diff.scala.html
--- app/views/git/partial_diff.scala.html
+++ app/views/git/partial_diff.scala.html
@@ -5,11 +5,9 @@
 @import utils.AccessControl._
 @import models.enumeration
 @import playRepository.GitCommit
-@implicitField = @{ helper.FieldConstructor(simpleForm) }
+@import scala.collection.Map
 
-@branchName(branch:String) = @{
-    branch.replace("refs/heads/", "")
-}
+@implicitField = @{ helper.FieldConstructor(simpleForm) }
 
 @getCodeURL(project: Project) = @{
     if(session == null){
@@ -27,17 +25,7 @@
 
 <input type="text" name="title" id="title" placeholder="" maxlength="maxlength" tabindex="1" class="text" value="@pullRequest.title">
 <div style="position: relative;">
-    <div id="mode-select">
-        <input type="radio" name="edit-mode" id="edit-mode" value="edit" checked="checked" class="radio-btn">
-        <label for="edit-mode" style="margin-right:3px;">Edit</label>
-        <input type="radio" name="edit-mode" id="preview-mode" value="preview" class="radio-btn">
-        <label for="preview-mode">Preview</label>
-    </div>
-    <a href="#" class="go-zen"><i class="s s--zen">Edit in Zen Mode</i></a>
-    @help.markdown()
-    <div class="textarea-box">
-        <textarea id="body" name="body" markdown="true" class="zen-mode content" tabindex="2" >@pullRequest.body</textarea>
-    </div>
+    @common.editor("body", pullRequest.body, Map("tabindex"->"2"))
 </div>
 @common.fileUploader(ResourceType.PULL_REQUEST, null)
 @common.markdown(project)
app/views/issue/create.scala.html
--- app/views/issue/create.scala.html
+++ app/views/issue/create.scala.html
@@ -38,17 +38,7 @@
                 <div class="span9">
                     <dl>
                         <dd style="position: relative;">
-                            <div id="mode-select">
-                                <input type="radio" name="edit-mode" id="edit-mode" value="edit" checked="checked" class="radio-btn">
-                                <label for="edit-mode" style="margin-right:3px;">Edit</label>
-                                <input type="radio" name="edit-mode" id="preview-mode" value="preview" class="radio-btn">
-                                <label for="preview-mode">Preview</label>
-                            </div>
-                            <a href="#" class="go-zen"><i class="s s--zen">Edit in Zen Mode</i></a>
-                            @help.markdown()
-                            <div class="textarea-box">
-                                <textarea id="body" name="body" markdown="true" class="zen-mode content nm" tabindex="2" data-label="@Messages("issue.error.emptyBody")"></textarea>
-                            </div>
+                            @common.editor("body", "", Map("tabindex"->"2"))
                         </dd>
                     </dl>
 
app/views/issue/edit.scala.html
--- app/views/issue/edit.scala.html
+++ app/views/issue/edit.scala.html
@@ -39,17 +39,7 @@
                 <div class="span9">
                     <dl>
                         <dd style="position: relative;">
-                            <div id="mode-select">
-                                <input type="radio" name="edit-mode" id="edit-mode" value="edit" checked="checked" class="radio-btn">
-                                <label for="edit-mode" style="margin-right:3px;">Edit</label>
-                                <input type="radio" name="edit-mode" id="preview-mode" value="preview" class="radio-btn">
-                                <label for="preview-mode">Preview</label>
-                            </div>
-                            <a href="#" class="go-zen"><i class="s s--zen">Edit in Zen Mode</i></a>
-                            @help.markdown()
-                            <div class="textarea-box">
-                                <textarea id="body" name="body" markdown="true" class="zen-mode content nm" tabindex="2">@issue.body</textarea>
-                            </div>
+                            @common.editor("body", issue.body, Map("tabindex"->"2"))
                         </dd>
                     </dl>
 
app/views/issue/partial_comments.scala.html
--- app/views/issue/partial_comments.scala.html
+++ app/views/issue/partial_comments.scala.html
@@ -77,8 +77,7 @@
                 <img src="@User.findByLoginId(comment.authorLoginId).avatarUrl" width="32" height="32" alt="@comment.authorLoginId">
             </a>
         </div>
-        @common.commentUpdateForm(project, ResourceType.ISSUE_COMMENT, comment.id, routes.IssueApp.newComment(project.owner, project.name, issue.getNumber).toString(), comment.contents)
-        <div class="media-body" id="comment-body-@comment.id">
+        <div class="media-body">
             <div class="meta-info">
                 <span class="comment_author pull-left">
                     <a href="@routes.UserApp.userInfo(comment.authorLoginId)" data-toggle="tooltip" data-placement="top" title="@comment.authorName">
@@ -97,8 +96,12 @@
                 </span>
             </div>
 
-            <div class="comment-body markdown-wrap markdown-before" markdown="true">@comment.contents</div>
-            <div class="attachments pull-right" data-resourceType="@ResourceType.ISSUE_COMMENT" data-resourceId="@comment.id"></div>
+            @common.commentUpdateForm(comment.id, routes.IssueApp.newComment(project.owner, project.name, issue.getNumber).toString(), comment.contents)
+
+            <div id="comment-body-@comment.id">
+                <div class="comment-body markdown-wrap markdown-before" markdown="true">@comment.contents</div>
+                <div class="attachments pull-right" data-resourceType="@ResourceType.ISSUE_COMMENT" data-resourceId="@comment.id"></div>
+            </div>
         </div>
     </li>
 
app/views/milestone/create.scala.html
--- app/views/milestone/create.scala.html
+++ app/views/milestone/create.scala.html
@@ -2,6 +2,7 @@
 
 @import utils.TemplateHelper._
 @import models.enumeration._
+@import scala.collection.Map
 
 @projectLayout(title, project, utils.MenuType.MILESTONE) {
 @projectMenu(project, utils.MenuType.MILESTONE, "")
@@ -18,17 +19,7 @@
                         <div class="content-wrap">
                             <label for="contents">@Messages("milestone.form.content")</label>
                             <div style="position: relative;">
-                                <div id="mode-select">
-                                    <input type="radio" name="edit-mode" id="edit-mode" value="edit" checked="checked" class="radio-btn">
-                                    <label for="edit-mode" style="margin-right:3px;">Edit</label>
-                                    <input type="radio" name="edit-mode" id="preview-mode" value="preview" class="radio-btn">
-                                    <label for="preview-mode">Preview</label>
-                                </div>
-                                <a href="#" class="go-zen"><i class="s s--zen">Edit in Zen Mode</i></a>
-                                @help.markdown()
-                                <div class="textarea-box">
-                                    <textarea id="contents" class="content" name="contents" markdown="true"></textarea>
-                                </div>
+                                @common.editor("contents", "", Map("id"->"contents"))
                             </div>
                         </div>
 
app/views/milestone/edit.scala.html
--- app/views/milestone/edit.scala.html
+++ app/views/milestone/edit.scala.html
@@ -2,6 +2,8 @@
 
 @import utils.TemplateHelper._
 @import models.enumeration._
+@import scala.collection.Map
+
 @implicitField = @{ helper.FieldConstructor(simpleForm) }
 
 @projectLayout(title, project, utils.MenuType.MILESTONE) {
@@ -21,17 +23,7 @@
                     <label for="contents">@Messages("milestone.form.content")</label>
                     @helper.input(form("contents")){(id, name, value, args) =>
                     <div style="position: relative;">
-                        <div id="mode-select">
-                            <input type="radio" name="edit-mode" id="edit-mode" value="edit" checked="checked" class="radio-btn">
-                            <label for="edit-mode" style="margin-right:3px;">Edit</label>
-                            <input type="radio" name="edit-mode" id="preview-mode" value="preview" class="radio-btn">
-                            <label for="preview-mode">Preview</label>
-                        </div>
-                        <a href="#" class="go-zen"><i class="s s--zen">Edit in Zen Mode</i></a>
-                        @help.markdown()
-                        <div class="textarea-box">
-                            <textarea id="contents" class="content" id="@id" name="@name" markdown="true">@value</textarea>
-                        </div>
+                        @common.editor("contents", value.getOrElse(""), Map("id"->"contents"))
                     </div>
                     }
                 </div>
conf/messages
--- conf/messages
+++ conf/messages
@@ -128,6 +128,8 @@
 common.comment.delete = Delete Comment
 common.comment.delete.confirm = If this is deleted, then it cannot be recovered. Is it okay and go through?
 common.comment.edit = comment edit
+common.editor.edit = Edit
+common.editor.preview = Preview
 common.experimental = Experimental
 common.experimental.description = It means that can be changed or interrupted at any time.<br>Appreciate for your generosity.
 common.experimental.title = Experimental: this feature is on development.
conf/messages.ko
--- conf/messages.ko
+++ conf/messages.ko
@@ -128,6 +128,8 @@
 common.comment.delete = 댓글 삭제
 common.comment.delete.confirm = 해당 댓글이 삭제되면 영원히 복구할 수 없습니다. 그래도 삭제하시겠습니까?
 common.comment.edit = 댓글 수정
+common.editor.edit = 편집
+common.editor.preview = 미리보기
 common.experimental = 실험적인 기능
 common.experimental.description = 이 기능은 아직 개발 진행 중으로 언제든지 변경되거나 개발 중단될 수 있습니다.<br>너그러운 마음으로 응원해주세요.
 common.experimental.title = 실험적인 기능: 새롭게 개발 중인 기능을 선보입니다
public/javascripts/common/yobi.CodeCommentBox.js
--- public/javascripts/common/yobi.CodeCommentBox.js
+++ public/javascripts/common/yobi.CodeCommentBox.js
@@ -85,20 +85,6 @@
         htElement.welCommentForm.on("click", '[data-toggle="close"]', function(){
             _hide();
         });
-
-        htElement.welCommentForm.on("submit", function(){
-            _removeEmptyFieldsOnForm();
-        });
-    }
-
-    /**
-     * Remove empty INPUT elements
-     * @private
-     */
-    function _removeEmptyFieldsOnForm(){
-        htElement.welCommentForm.find("input").filter(function(){
-            return ($(this).val().length === 0);
-        }).remove();
     }
 
     /**
public/javascripts/common/yobi.Markdown.js
--- public/javascripts/common/yobi.Markdown.js
+++ public/javascripts/common/yobi.Markdown.js
@@ -9,7 +9,6 @@
 yobi.Markdown = (function(htOptions){
 
     var htVar = {};
-    var htElement = {};
 
     /**
      * initialize
@@ -27,10 +26,10 @@
      * @param {Hash Table} htOptions
      */
     function _initVar(htOptions){
-        htVar.sTplSwitch = htOptions.sTplSwitch;
         htVar.sIssuesUrl = htOptions.sIssuesUrl;
         htVar.sProjectUrl = htOptions.sProjectUrl;
         htVar.bBreaks = htOptions.bBreaks;
+
         htVar.sUserRules = '[a-z0-9_\\-\\.]';
         htVar.sProjecRules = '[a-z0-9_\\-\\.]';
         htVar.sIssueRules = '\\d';
@@ -176,31 +175,33 @@
     }
 
     /**
+     * set Markdown Viewer
+     *
+     * @param {Wrapped Element} welTarget is not <textarea> or <input>
+     */
+    function _setViewer(welTarget) {
+        var sMarkdownText = welTarget.text();
+        var sContentBody  = (sMarkdownText) ? _renderMarkdown(sMarkdownText) : welTarget.html();
+        welTarget.html(sContentBody).removeClass('markdown-before');
+    }
+
+    /**
      * set Markdown Editor
      *
      * @param {Wrapped Element} welTextarea
      */
-    function _setEditor(welTextarea) {
-        // create new preview area
-        var welPreview = $('<div class="markdown-preview markdown-wrap">');
-        var welTextareaBox = welTextarea.parents('.write-comment-wrap').find('.textarea-box');
+    function _setEditor(welTextarea){
+        var elContainer = welTextarea.parents('[data-toggle="markdown-editor"]').get(0);
 
-        welPreview.css({
-            "display"   : "none",
-            "min-height": welTextarea.height() + 'px'
-        });
+        if(!elContainer){
+            return false;
+        }
 
-        var welPreviewSwitch = welTextarea.parents('.write-comment-wrap').find('input[name="edit-mode"]');
-
-        var fOnChangeSwitch = function() {
-            var bPreview = (welTextarea.parents('.write-comment-wrap').find("input:radio[name=edit-mode]:checked").val() == "preview");
+        $(elContainer).on("click", 'a[data-mode="preview"]', function(weEvt){
+            var welPreview = $(weEvt.delegateTarget).find("div.markdown-preview");
             welPreview.html(_renderMarkdown(welTextarea.val()));
-            (bPreview ? welPreview: welTextareaBox).show();
-            (bPreview ? welTextareaBox: welPreview).hide();
-        };
-
-        welPreviewSwitch.change(fOnChangeSwitch);
-        welTextareaBox.before(welPreview);
+            welPreview.css({"min-height": welTextarea.height() + 'px'});
+        });
 
         welTextarea.on("keydown.tabkey-event-handler", function(e) {
             if(e.keyCode === 9){ //tab
@@ -211,17 +212,6 @@
                 this.selectionEnd = start + 1;
             }
         });
-    }
-
-    /**
-     * set Markdown Viewer
-     *
-     * @param {Wrapped Element} welTarget is not <textarea> or <input>
-     */
-    function _setViewer(welTarget) {
-        var sMarkdownText = welTarget.text();
-        var sContentBody  = (sMarkdownText) ? _renderMarkdown(sMarkdownText) : welTarget.html();
-        welTarget.html(sContentBody).removeClass('markdown-before');
     }
 
     /**
@@ -238,7 +228,7 @@
     }
 
     /**
-     * Returns that specifieid element is editable
+     * Returns that specified element is editable
      *
      * @param {HTMLElement} elTarget
      * @return {Boolean}
public/javascripts/service/yobi.issue.Write.js
--- public/javascripts/service/yobi.issue.Write.js
+++ public/javascripts/service/yobi.issue.Write.js
@@ -27,9 +27,6 @@
 
             // 제목 입력란에 포커스
             htElement.welInputTitle.focus();
-
-            // zenForm
-            _initZenForm();
         }
 
         /**
@@ -136,19 +133,6 @@
 
             $(window).off("beforeunload", _onBeforeUnload);
             return true;
-        }
-
-        /**
-         * ZenForm 초기화
-         * initialize zenForm
-         */
-        function _initZenForm(){
-            $(".zen-mode").zenForm({"theme": "light"});
-            $(".s--zen").tooltip({
-                "delay": {"show": 500, "hide": 100},
-                "title": Messages("title.zenmode"),
-                "placement": "left"
-            });
         }
 
         _init(htOptions);
Add a comment
List