doortts doortts 2017-04-03
view: Introduce two column view mode
@c794532c98ea0c0a1cd2f6a75747853c8233e87d
app/assets/stylesheets/less/_page.less
--- app/assets/stylesheets/less/_page.less
+++ app/assets/stylesheets/less/_page.less
@@ -3224,7 +3224,7 @@
 
     .project-btn-item {
         a {
-            width: 196px;
+            width: 90%;
             margin-bottom: 15px;
         }
     }
@@ -6811,3 +6811,49 @@
 #new-file-link {
     margin: 0 5px;
 }
+
+.two-column-icon {
+    display: inline-block;
+    line-height: 30px;
+    margin-left: 10px;
+    i {
+        font-size: 16px;
+        margin-right: 5px;
+        line-height: 20px;
+    }
+
+    label {
+        padding-top: 7px;
+    }
+}
+
+.absoluteAndLeftMargin {
+    position: absolute;
+    margin-left: 260px;
+}
+
+.page-nums {
+    margin-left: -120px !important;
+}
+
+#pageslide {
+    /* These styles MUST be included. Do not change. */
+    display: none;
+    position: absolute;
+    position: fixed;
+    top: 0;
+    height: 100%;
+    z-index: 999999;
+
+    /* Specify the width of your pageslide here */
+    width: 50%;
+    padding: 1px;
+
+    /* These styles are optional, and describe how the pageslide will look */
+    background-color: #333;
+    color: #FFF;
+    -webkit-box-shadow: inset 0 0 5px 5px #222;
+    -moz-shadow: inset 0 0 5px 5px #222;
+    //box-shadow: inset 0 0 5px 5px #222;
+    box-shadow: 2px 2px 8px #000;
+}
app/assets/stylesheets/less/_responsive.less
--- app/assets/stylesheets/less/_responsive.less
+++ app/assets/stylesheets/less/_responsive.less
@@ -433,6 +433,9 @@
             }
         }
     }
+    .page-nums {
+        margin-left: 0;
+    }
 }
 
 @media all and (max-width:1199px){
@@ -534,7 +537,6 @@
     }
     .project-header-outer {
         max-width: 1199px;
-        min-width: 1000px;
 
         .project-header-inner {
             .project-header-wrap {
@@ -546,8 +548,6 @@
 
     .project-menu-outer {
         max-width: 1199px;
-        min-width: 1000px;
-        padding:0 10px;
 
         .project-menu-inner {
             width: 95%;
app/views/board/list.scala.html
--- app/views/board/list.scala.html
+++ app/views/board/list.scala.html
@@ -1,22 +1,8 @@
 @**
-* Yobi, Project Hosting SW
+* Yona, 21st Century Project Hosting SW
 *
-* Copyright 2012 NAVER Corp.
-* http://yobi.io
-*
-* @author Sangcheol Hwang
-*
-* 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.
+* Copyright Yona & Yobi Authors & NAVER Corp.
+* https://yona.io
 **@
 @(title:String, project:Project, page:com.avaje.ebean.Page[Posting], param:BoardApp.SearchCondition, notices:List[models.Posting])
 
@@ -49,11 +35,14 @@
                     <input name="filter" class="textbox" type="text" placeholder="@Messages("project.searchPlaceholder")" value="@param.filter">
                     <button type="submit" class="search-btn"><i class="yobicon-search"></i></button>
                 </div>
-                <div class="board-labels">
-                @if(!IssueLabel.findByProject(project).isEmpty){
-                    @issue.partial_select_label(IssueLabel.findByProject(project), param.labelIdSet, "", "", project)
+                @defining(IssueLabel.findByProject(project)) { labels =>
+                        @if(!labels.isEmpty) {
+                            <div class="board-labels">
+                            @issue.partial_select_label(labels, param.labelIdSet, "", "", project)
+                            </div>
+                        }
                 }
-                </div>
+                @common.twoColumnModeCheckboxArea(false)
             </form>
             <div class="pull-right">
                 <a href="@routes.BoardApp.newPostForm(project.owner, project.name)" class="ybtn ybtn-success">@Messages("post.write")</a>
@@ -101,6 +90,8 @@
         @help.keymap("boardList", project)
     </div>
 </div>
+<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/jquery.pageslide.js")"></script>
+<script type="text/javascript" src="@routes.Assets.at("javascripts/service/yona.twoColumnMode.js")"></script>
 <script type="text/javascript">
 	$(document).ready(function() {
 		$yobi.loadModule("board.List", {
app/views/board/partial_list.scala.html
--- app/views/board/partial_list.scala.html
+++ app/views/board/partial_list.scala.html
@@ -1,29 +1,15 @@
 @**
-* Yobi, Project Hosting SW
+* Yona, 21st Century Project Hosting SW
 *
-* Copyright 2013 NAVER Corp.
-* http://yobi.io
-*
-* @author Suwon Chae
-*
-* 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.
+* Copyright Yona & Yobi Authors & NAVER Corp.
+* https://yona.io
 **@
 @(post:models.Posting, project:Project)
 
 @import utils.JodaDateUtil
 @import utils.TemplateHelper._
 @defining(User.findByLoginId(post.authorLoginId)){ user =>
-<li class="post-item">
+<li class="post-item title" href="@routes.BoardApp.post(project.owner, project.name, post.getNumber)">
     <a href="@routes.UserApp.userInfo(user.loginId)" class="avatar-wrap mlarge hide-in-mobile" data-toggle="tooltip" data-placement="bottom" title="@user.loginId">
         <img src="@user.avatarUrl(64)" alt="@user.name" width="32" height="32"/>
     </a>
app/views/common/scripts.scala.html
--- app/views/common/scripts.scala.html
+++ app/views/common/scripts.scala.html
@@ -151,7 +151,7 @@
 
         yobi.OriginalMessage.hide($("[data-via-email]"));
 
-        $('.logo, .title > a, .project-menu-nav > li > a, .show-progress-bar, .project-breadcrumb > span > a, .project-name > a, a[class="title"]').on('click', function(){
+        $('.logo, .title > a, .project-menu-nav > li > a, .show-progress-bar, .project-breadcrumb > span > a, .project-name > a, a[class="title"]').on('click.progressbar', function(){
             NProgress.set(0.7);
             NProgress.start();
         });
 
app/views/common/twoColumnModeCheckboxArea.scala.html (added)
+++ app/views/common/twoColumnModeCheckboxArea.scala.html
@@ -0,0 +1,20 @@
+@**
+* Yona, 21st Century Project Hosting SW
+*
+* Copyright Yona & Yobi Authors & NAVER Corp.
+* https://yona.io
+**@
+@(requireLeftMargin: Boolean = true)
+
+@additionalCssForLeftMargin = @{
+    if(requireLeftMargin) {
+        "pull-left absoluteAndLeftMargin"
+    } else {
+        ""
+    }
+}
+<div class="@additionalCssForLeftMargin two-column-icon mr10 hide-in-mobile" id="two-column-mode-checkbox" title='@Messages("common.two.column.mode")' data-content='@Messages("common.two.column.mode.desc")'>
+    <label class="checkbox">
+        <input id="two-column-mode" type="checkbox"> <i class="yobicon-explorerwindow"></i>
+    </label>
+</div>
app/views/git/list.scala.html
--- app/views/git/list.scala.html
+++ app/views/git/list.scala.html
@@ -1,22 +1,8 @@
 @**
-* Yobi, Project Hosting SW
+* Yona, 21st Century Project Hosting SW
 *
-* Copyright 2013 NAVER Corp.
-* http://yobi.io
-*
-* @author Keesun Baik
-*
-* 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.
+* Copyright Yona & Yobi Authors & NAVER Corp.
+* https://yona.io
 **@
 @(project: Project, page: com.avaje.ebean.Page[PullRequest],
     condition: controllers.PullRequestApp.SearchCondition, requestType: String)
@@ -31,8 +17,12 @@
 
 @common.select2()
 
+<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/jquery.pageslide.js")"></script>
+<script type="text/javascript" src="@routes.Assets.at("javascripts/service/yona.twoColumnMode.js")"></script>
 <script type="text/javascript">
     $(document).ready(function(){
+        _initTwoColumnMode();
+
         var isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
         var isSafari = navigator.userAgent.indexOf('Safari') != -1 && navigator.userAgent.indexOf('Chrome') == -1;
         // Workaround for pjax bug result from bfcache
app/views/git/partial_list.scala.html
--- app/views/git/partial_list.scala.html
+++ app/views/git/partial_list.scala.html
@@ -1,22 +1,8 @@
 @**
-* Yobi, Project Hosting SW
+* Yona, 21st Century Project Hosting SW
 *
-* Copyright 2013 NAVER Corp.
-* http://yobi.io
-*
-* @author Keesun Baik
-*
-* 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.
+* Copyright Yona & Yobi Authors & NAVER Corp.
+* https://yona.io
 **@
 @(project: Project, page: com.avaje.ebean.Page[PullRequest])
 
@@ -28,7 +14,7 @@
 @if(page.getList.size> 0){
     @for(req <- page.getList.iterator) {
     @defining(User.findByLoginId(req.contributor.loginId)){ user =>
-        <li class="post-item">
+        <li class="post-item title" href="@routes.PullRequestApp.pullRequest(req.toProject.owner, req.toProject.name, req.number)">
             <div class="span10 span-hard-wrap">
                 <a href="@routes.UserApp.userInfo(user.loginId)" class="avatar-wrap mlarge" data-toggle="tooltip" data-placement="top" title="@user.loginId">
                     @if(user.avatarUrl == UserApp.DEFAULT_AVATAR_URL){
app/views/git/partial_search.scala.html
--- app/views/git/partial_search.scala.html
+++ app/views/git/partial_search.scala.html
@@ -45,7 +45,7 @@
 }
 
   <div pjax-container class="row-fluid cb">
-    <div class="span2 search-wrap hide-in-mobile" style="padding-top:0;">
+    <div class="left-menu span2 search-wrap hide-in-mobile" style="padding-top:0;">
       <form id="search" name="search" action="@searchFormAction(condition.category)" method="get">
         <div class="search">
           <div class="search-bar">
@@ -88,6 +88,7 @@
           @partial_recently_pushed_branches(PushedBranch.findByOwnerAndOriginalProject(UserApp.currentUser, project))
         }
       }
+      @common.twoColumnModeCheckboxArea()
       <div class="pull-right">
         <a href="@routes.PullRequestApp.newPullRequestForm(project.owner, project.name)" class="ybtn ybtn-success">
             @Messages("pullRequest.new")
app/views/issue/list.scala.html
--- app/views/issue/list.scala.html
+++ app/views/issue/list.scala.html
@@ -20,6 +20,8 @@
 @common.select2()
 @common.calendar()
 
+<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/jquery.pageslide.js")"></script>
+<script type="text/javascript" src="@routes.Assets.at("javascripts/service/yona.twoColumnMode.js")"></script>
 <script type="text/javascript">
   $(function(){
     // issue.List
app/views/issue/my_list.scala.html
--- app/views/issue/my_list.scala.html
+++ app/views/issue/my_list.scala.html
@@ -19,6 +19,8 @@
             @views.html.issue.my_partial_search(title, currentPage, param, project)
         </div>
     </div>
+    <script type="text/javascript" src="@routes.Assets.at("javascripts/lib/jquery.pageslide.js")"></script>
+    <script type="text/javascript" src="@routes.Assets.at("javascripts/service/yona.twoColumnMode.js")"></script>
     <script type="text/javascript">
       $(function(){
           $yobi.loadModule("issue.List");
app/views/issue/my_partial_list.scala.html
--- app/views/issue/my_partial_list.scala.html
+++ app/views/issue/my_partial_list.scala.html
@@ -18,7 +18,7 @@
 @for(issue <- issueList){
     @defining(issue.project){ project =>
     @defining(User.findByLoginId(issue.authorLoginId)){ user =>
-    <li class="post-item" id="issue-item-@issue.id">
+    <li class="post-item title" id="issue-item-@issue.id" href="@routes.IssueApp.issue(project.owner, project.name, issue.getNumber)">
         <div class="span10 span-hard-wrap">
             <a href="@routes.UserApp.userInfo(user.loginId)" class="avatar-wrap mlarge hide-in-mobile" data-toggle="tooltip" data-placement="bottom" title="@user.loginId">
                 @if(user.avatarUrl == UserApp.DEFAULT_AVATAR_URL){
app/views/issue/my_partial_search.scala.html
--- app/views/issue/my_partial_search.scala.html
+++ app/views/issue/my_partial_search.scala.html
@@ -1,22 +1,8 @@
 @**
-* Yobi, Project Hosting SW
+* Yona, 21st Century Project Hosting SW
 *
-* Copyright 2014 NAVER Corp.
-* http://yobi.io
-*
-* @author Suwon Chae
-*
-* 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.
+* Copyright Yona & Yobi Authors & NAVER Corp.
+* https://yona.io
 **@
 @(title: String, currentPage: com.avaje.ebean.Page[Issue], param:models.support.SearchCondition, project:Project)
 
@@ -34,7 +20,7 @@
 }
 
 <div pjax-container class="row-fluid issue-list-wrap">
-    <div class="span2 span-hard-wrap">
+    <div class="left-menu span2 span-hard-wrap">
         <div class="inner advanced">
             @my_partial_list_quicksearch(param)
 
@@ -67,6 +53,7 @@
         </div>
     </div>
     <div class="span10 span-hard-wrap" id="span10">
+        @common.twoColumnModeCheckboxArea()
         <ul class="nav nav-tabs nm">
         @for(state <- Array(State.OPEN, State.CLOSED)) {
             <li @if(param.state == state.state) { class="active" } data-pjax>
app/views/issue/partial_list.scala.html
--- app/views/issue/partial_list.scala.html
+++ app/views/issue/partial_list.scala.html
@@ -19,14 +19,14 @@
 <ul class="post-list-wrap row-fluid">
 @for(issue <- issueList){
     @defining(User.findByLoginId(issue.authorLoginId)){ user =>
-    <li class="post-item" id="issue-item-@issue.id" data-item="issue-item" data-value="@user.loginId @issue.getNumber @issue.title">
+    <li class="post-item title" id="issue-item-@issue.id" data-item="issue-item" data-value="@user.loginId @issue.getNumber @issue.title" href="@routes.IssueApp.issue(project.owner, project.name, issue.getNumber)">
         <div class="span9 span-hard-wrap">
             @if(ProjectUser.isMember(UserApp.currentUser().id, project.id)){
             <label for="issue-@issue.id" class="mass-update-check hide-in-mobile">
                 <input id="issue-@issue.id" type="checkbox" name="checked-issue" data-toggle="issue-checkbox" data-issue-id="@issue.id" data-issue-labels="@issueLabels(issue)">
             </label>
             }
-            <label for="issue-@issue.id" class="issue-item-row">
+            <div for="issue-@issue.id" class="issue-item-row">
                 <a href="@routes.UserApp.userInfo(user.loginId)" class="avatar-wrap mlarge hide-in-mobile" data-toggle="tooltip" data-placement="top" title="@user.loginId">
                         <img src="@User.findByLoginId(issue.authorLoginId).avatarUrl(64)" alt="@user.name" width="32" height="32"/>
                 </a>
@@ -77,7 +77,7 @@
                         <a href="#" class="label issue-label list-label active" data-category-id="@label.category.id" data-label-id="@label.id">@label.name</a>
                     }
                 </div>
-            </label>
+            </div>
         </div>
         <div class="span3 hide-in-mobile">
             <div class="mt5 pull-right">
app/views/issue/partial_list_wrap.scala.html
--- app/views/issue/partial_list_wrap.scala.html
+++ app/views/issue/partial_list_wrap.scala.html
@@ -37,7 +37,7 @@
 }
 
 <div pjax-container class="row-fluid issue-list-wrap">
-    <div class="span2 span-hard-wrap">
+    <div class=" left-menu span2 span-hard-wrap">
         @partial_list_quicksearch(param, project)
         @partial_searchform(param, project)
     </div>
@@ -45,7 +45,7 @@
         <div class="pull-right">
             <a href="@routes.IssueApp.newIssueForm(project.owner, project.name)" class="ybtn ybtn-success">@Messages("issue.menu.new")</a>
         </div>
-
+        @common.twoColumnModeCheckboxArea()
         <ul class="nav nav-tabs nm">
         @for(state <- Array(State.OPEN, State.CLOSED)) {
             <li @if(param.state == state.state) { class="active" } data-pjax>
app/views/organization/group_board_list.scala.html
--- app/views/organization/group_board_list.scala.html
+++ app/views/organization/group_board_list.scala.html
@@ -1,22 +1,8 @@
 @**
-* Yobi, Project Hosting SW
+* Yona, 21st Century Project Hosting SW
 *
-* Copyright 2015 NAVER Corp.
-* http://yobi.io
-*
-* @author Suwon Chae
-*
-* 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.
+* Copyright Yona & Yobi Authors & NAVER Corp.
+* https://yona.io
 **@
 @(title:String, organization:Organization, page:com.avaje.ebean.Page[Posting], param:BoardApp.SearchCondition, notices:List[models.Posting])
 
@@ -56,6 +42,7 @@
                     <input name="filter" class="textbox group-board" type="text" placeholder="@Messages("title.searchByKeyword")" value="@param.filter">
                     <button type="submit" class="search-btn"><i class="yobicon-search"></i></button>
                 </div>
+                @common.twoColumnModeCheckboxArea(false)
             </form>
         </div>
 
@@ -99,6 +86,8 @@
 
     </div>
 </div>
+<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/jquery.pageslide.js")"></script>
+<script type="text/javascript" src="@routes.Assets.at("javascripts/service/yona.twoColumnMode.js")"></script>
 <script type="text/javascript">
 	$(document).ready(function() {
 		$yobi.loadModule("board.List", {
app/views/organization/group_board_list_partial.scala.html
--- app/views/organization/group_board_list_partial.scala.html
+++ app/views/organization/group_board_list_partial.scala.html
@@ -23,7 +23,7 @@
 @import utils.JodaDateUtil
 @import utils.TemplateHelper._
 @defining(User.findByLoginId(post.authorLoginId)){ user =>
-<li class="post-item">
+<li class="post-item title" href="@routes.BoardApp.post(project.owner, project.name, post.getNumber)">
     <a href="@routes.UserApp.userInfo(user.loginId)" class="avatar-wrap mlarge hide-in-mobile" data-toggle="tooltip" data-placement="top" title="@user.loginId">
         @if(user.avatarUrl == UserApp.DEFAULT_AVATAR_URL){
             <img src="@urlToPicture(user.email, 32)">
app/views/organization/group_issue_list.scala.html
--- app/views/organization/group_issue_list.scala.html
+++ app/views/organization/group_issue_list.scala.html
@@ -1,22 +1,8 @@
 @**
-* Yobi, Project Hosting SW
+* Yona, 21st Century Project Hosting SW
 *
-* Copyright 2015 NAVER Corp.
-* http://yobi.io
-*
-* @author Suwon Chae
-*
-* 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.
+* Copyright Yona & Yobi Authors & NAVER Corp.
+* https://yona.io
 **@
 @(title: String, currentPage: com.avaje.ebean.Page[Issue], param:
 models.support.SearchCondition, organization: Organization)
@@ -31,6 +17,8 @@
             @views.html.organization.group_issue_search_partial(title, currentPage, param, organization)
         </div>
     </div>
+    <script type="text/javascript" src="@routes.Assets.at("javascripts/lib/jquery.pageslide.js")"></script>
+    <script type="text/javascript" src="@routes.Assets.at("javascripts/service/yona.twoColumnMode.js")"></script>
     <script type="text/javascript">
       $(function(){
         $yobi.loadModule("issue.List");
app/views/organization/group_issue_list_partial.scala.html
--- app/views/organization/group_issue_list_partial.scala.html
+++ app/views/organization/group_issue_list_partial.scala.html
@@ -31,7 +31,7 @@
 <ul class="post-list-wrap">
 @for(issue <- issueList){
 @defining(User.findByLoginId(issue.authorLoginId)){ user =>
-    <li class="post-item" id="issue-item-@issue.id">
+    <li class="post-item title" id="issue-item-@issue.id" href="@routes.IssueApp.issue(issue.project.owner, issue.project.name, issue.getNumber)">
         <div class="span10 span-hard-wrap">
             <a href="@routes.UserApp.userInfo(user.loginId)" class="avatar-wrap mlarge hide-in-mobile" data-toggle="tooltip" data-placement="top" title="@user.loginId">
                 @if(user.avatarUrl == UserApp.DEFAULT_AVATAR_URL){
app/views/organization/group_issue_search_partial.scala.html
--- app/views/organization/group_issue_search_partial.scala.html
+++ app/views/organization/group_issue_search_partial.scala.html
@@ -34,7 +34,7 @@
 }
 
 <div pjax-container class="row-fluid issue-list-wrap">
-    <div class="span2 span-hard-wrap">
+    <div class="left-menu span2 span-hard-wrap">
         <div class="inner advanced">
             @group_issue_list_quicksearch(param)
 
@@ -64,6 +64,7 @@
         </div>
     </div>
     <div class="span10 span-hard-wrap" id="span10">
+        @common.twoColumnModeCheckboxArea()
         <ul class="nav nav-tabs nm">
         @for(state <- Array(State.OPEN, State.CLOSED)) {
             <li @if(param.state == state.state) { class="active" }>
conf/messages
--- conf/messages
+++ conf/messages
@@ -189,6 +189,8 @@
 common.time.second = {0} second ago
 common.time.seconds = {0} seconds ago
 common.time.today = Today
+common.two.column.mode = Two Column Mode
+common.two.column.mode.desc = Splits list and body into columns respectively
 emails.click.link = To check email, please click this link.
 emails.main.email = Primary email address
 emails.main.email.descr = Your primary email address will be used to send you notifications or reset your passwords.
conf/messages.ko-KR
--- conf/messages.ko-KR
+++ conf/messages.ko-KR
@@ -190,6 +190,8 @@
 common.time.second = {0}초 전
 common.time.seconds = {0}초 전
 common.time.today = 오늘
+common.two.column.mode = Two Column Mode
+common.two.column.mode.desc = 리스트와 본문을 각각 컬럼으로 분할해서 보여줍니다
 common.unselect = 선택안함
 emails.click.link = 이메일을 확인하려면 다음 링크를 클릭하세요.
 emails.main.email = 대표 이메일
public/javascripts/lib/jquery.pageslide.js
--- public/javascripts/lib/jquery.pageslide.js
+++ public/javascripts/lib/jquery.pageslide.js
@@ -1,0 +1,220 @@
+/*
+ * jQuery pageSlide
+ * Version 2.0.1
+ * https://github.com/tacitknowledge/jquery-pageslide
+ *
+ *
+ *
+ *
+ * Copyright (c) 2011 Scott Robbin (srobbin.com)
+ * Modified by Tacit Knowledge for CommonJS and AMD integration 2015
+ * Dual licensed under the MIT and GPL licenses.
+ */
+
+(function(factory){
+    if (typeof define === 'function' && define.amd) {
+        define(function () {
+            return factory;
+        });
+    } else if (typeof module === 'object' && typeof module.exports === 'object') {
+        exports = factory;
+    } else {
+        factory(jQuery);
+    }
+})(function($){
+    // Not inintializing second time
+    if ($.fn.pageslide) {
+        return;
+    }
+    // Convenience vars for accessing elements
+    var $body = $('body'),
+        $pageslide = $('#pageslide');
+
+    var _sliding = false,   // Mutex to assist closing only once
+        _lastCaller;        // Used to keep track of last element to trigger pageslide
+
+    // If the pageslide element doesn't exist, create it
+    if( $pageslide.length == 0 ) {
+        $pageslide = $('<div />').attr( 'id', 'pageslide' )
+            .css( 'display', 'none' )
+            .appendTo( $body );
+    }
+
+    /*
+     * Private methods
+     */
+    function _load( url, useIframe ) {
+        // Are we loading an element from the page or a URL?
+        if ( url.indexOf("#") === 0 ) {
+            // Load a page element
+            $(url).clone(true).appendTo( $pageslide.empty() ).show();
+        } else {
+            // Load a URL. Into an iframe?
+            if( useIframe ) {
+                var iframe = $("<iframe />").attr({
+                    src: url,
+                    frameborder: 0,
+                    hspace: 0
+                })
+                    .css({
+                        width: "100%",
+                        height: "100%"
+                    });
+
+                $pageslide.html( iframe );
+            } else {
+                $pageslide.load( url );
+            }
+
+            $pageslide.data( 'localEl', false );
+
+        }
+    }
+
+    // Function that controls opening of the pageslide
+    function _start( direction, speed ) {
+        var slideWidth = $pageslide.outerWidth( true ),
+            bodyAnimateIn = {},
+            slideAnimateIn = {};
+
+        // If the slide is open or opening, just ignore the call
+        if( $pageslide.is(':visible') || _sliding ) return;
+        _sliding = true;
+
+        switch( direction ) {
+            case 'left':
+                $pageslide.css({ left: 'auto', right: '-' + slideWidth + 'px' });
+                // bodyAnimateIn['margin-left'] = '-=' + slideWidth;
+                slideAnimateIn['right'] = '+=' + slideWidth;
+                break;
+            default:
+                $pageslide.css({ left: '-' + slideWidth + 'px', right: 'auto' });
+                bodyAnimateIn['margin-left'] = '+=' + slideWidth;
+                slideAnimateIn['left'] = '+=' + slideWidth;
+                break;
+        }
+
+        // Animate the slide, and attach this slide's settings to the element
+        $body.animate(bodyAnimateIn, speed);
+        $pageslide.show()
+            .animate(slideAnimateIn, speed, function() {
+                _sliding = false;
+            });
+    }
+
+    /*
+     * Declaration
+     */
+    $.fn.pageslide = function(options) {
+        var $elements = this;
+
+        // On click
+        $elements.on('click.pageslide', function(e) {
+            var $self = $(this),
+                settings = $.extend({ href: $self.attr('href') }, options);
+
+            // Prevent the default behavior and stop propagation
+            e.preventDefault();
+            e.stopPropagation();
+
+            if ( $pageslide.is(':visible') && $self[0] == _lastCaller ) {
+                // If we clicked the same element twice, toggle closed
+                $.pageslide.close();
+            } else {
+                // Open
+                $.pageslide( settings );
+
+                // Record the last element to trigger pageslide
+                _lastCaller = $self[0];
+            }
+        });
+    };
+
+    /*
+     * Default settings
+     */
+    $.fn.pageslide.defaults = {
+        speed:      200,        // Accepts standard jQuery effects speeds (i.e. fast, normal or milliseconds)
+        direction:  'right',    // Accepts 'left' or 'right'
+        modal:      false,      // If set to true, you must explicitly close pageslide using $.pageslide.close();
+        iframe:     true,       // By default, linked pages are loaded into an iframe. Set this to false if you don't want an iframe.
+        href:       null        // Override the source of the content. Optional in most cases, but required when opening pageslide programmatically.
+    };
+
+    /*
+     * Public methods
+     */
+
+    // Open the pageslide
+    $.pageslide = function( options ) {
+        // Extend the settings with those the user has provided
+        var settings = $.extend({}, $.fn.pageslide.defaults, options);
+
+        // Are we trying to open in different direction?
+        if( $pageslide.is(':visible') && $pageslide.data( 'direction' ) != settings.direction) {
+            $.pageslide.close(function(){
+                _load( settings.href, settings.iframe );
+                _start( settings.direction, settings.speed );
+            });
+        } else {
+            _load( settings.href, settings.iframe );
+            if( $pageslide.is(':hidden') ) {
+                _start( settings.direction, settings.speed );
+            }
+        }
+
+        $pageslide.data( settings );
+    }
+
+    // Close the pageslide
+    $.pageslide.close = function( callback ) {
+        var $pageslide = $('#pageslide'),
+            slideWidth = $pageslide.outerWidth( true ),
+            speed = $pageslide.data( 'speed' ),
+            bodyAnimateIn = {},
+            slideAnimateIn = {}
+
+        // If the slide isn't open, just ignore the call
+        if( $pageslide.is(':hidden') || _sliding ) return;
+        _sliding = true;
+
+        switch( $pageslide.data( 'direction' ) ) {
+            case 'left':
+                // bodyAnimateIn['margin-left'] = '+=' + slideWidth;
+                slideAnimateIn['right'] = '-=' + slideWidth;
+                break;
+            default:
+                bodyAnimateIn['margin-left'] = '-=' + slideWidth;
+                slideAnimateIn['left'] = '-=' + slideWidth;
+                break;
+        }
+
+        $pageslide.animate(slideAnimateIn, speed);
+        $body.animate(bodyAnimateIn, speed, function() {
+            $pageslide.hide();
+            _sliding = false;
+            if( typeof callback != 'undefined' ) callback();
+        });
+
+        $(".left-menu").show(200);
+    }
+
+    /* Events */
+
+    // Don't let clicks to the pageslide close the window
+    $pageslide.on('click.pageslide', function(e) {
+        e.stopPropagation();
+    });
+
+    // Close the pageslide if the document is clicked or the users presses the ESC key, unless the pageslide is modal
+    $(document).bind('click keyup', function(e) {
+        // If this is a keyup event, let's see if it's an ESC key
+        if( e.type == "keyup" && e.keyCode != 27) return;
+
+        // Make sure it's visible, and we're not modal
+        if( $pageslide.is( ':visible' ) && !$pageslide.data( 'modal' ) ) {
+            $.pageslide.close();
+        }
+    });
+
+});
public/javascripts/service/yobi.board.List.js
--- public/javascripts/service/yobi.board.List.js
+++ public/javascripts/service/yobi.board.List.js
@@ -1,23 +1,4 @@
-/**
- * Yobi, Project Hosting SW
- *
- * Copyright 2012 NAVER Corp.
- * http://yobi.io
- *
- * @author Ahn Hyeok Jun
- *
- * 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.
- */
+
 (function(ns){
 
     var oNS = $yobi.createNamespace(ns);
@@ -44,6 +25,7 @@
             _initPagination(htOptions);
             _initImplicitTitlePrefix();
             _listHoverEffect();
+            _initTwoColumnMode();
         }
 
         function _listHoverEffect(){
public/javascripts/service/yobi.issue.List.js
--- public/javascripts/service/yobi.issue.List.js
+++ public/javascripts/service/yobi.issue.List.js
@@ -46,6 +46,7 @@
             _initPjax();
             _initImplicitTitlePrefix();
             _listHoverEffect();
+            _initTwoColumnMode();
 
             htInitialOptions = htOptions || {};
         }
@@ -250,6 +251,7 @@
             _initImplicitTitlePrefix();
             _addEventAtOrganizationIssueSearchPage();
             _listHoverEffect();
+            _initTwoColumnMode();  // yona.twoColumnMode.js
         }
 
         function _listHoverEffect(){
 
public/javascripts/service/yona.twoColumnMode.js (added)
+++ public/javascripts/service/yona.twoColumnMode.js
@@ -0,0 +1,59 @@
+/**
+ * Yona, 21st Century Project Hosting SW
+ * <p>
+ * Copyright Yona & Yobi Authors & NAVER Corp.
+ * https://yona.io
+ **/
+function _initTwoColumnMode(){
+    var $twoColumnMode = $("#two-column-mode");
+    var useTwoColumnMode = localStorage.getItem('useTwoColumnMode');
+    var $title = $('.title');
+
+    $('#two-column-mode-checkbox').popover({trigger: "hover", placement: "top", delay: { show: 1000, hide: 100 }});
+
+    if( useTwoColumnMode  === 'true'){
+        attachPageSlideEvent($twoColumnMode, $title);
+        bindFrameLoading();
+    } else {
+        $twoColumnMode.prop('checked', false);
+        $('.post-item').css("cursor", "");
+        unbindEvents();
+    }
+
+    $twoColumnMode.on('click', function () {
+        console.log('this.checked', this.checked);
+        if(this.checked){
+            localStorage.setItem('useTwoColumnMode', true);
+            attachPageSlideEvent($twoColumnMode, $title);
+            bindFrameLoading();
+        } else {
+            localStorage.setItem('useTwoColumnMode', false);
+            $('.post-item').css("cursor", "");
+            unbindEvents();
+        }
+    });
+
+    ////////////////////////////
+
+    function attachPageSlideEvent(twoColumnMode, title){
+        twoColumnMode.prop('checked', true);
+        title.pageslide({direction: "left"});
+        $('.post-item').css("cursor", "pointer");
+    }
+
+    function unbindEvents() {
+        $title.unbind('click.pageslide');
+        $title.unbind('click.iframeLoading');
+    }
+
+    function bindFrameLoading() {
+        $title.on('click.iframeLoading', function () {
+            $(".left-menu").hide(200);
+            setTimeout(function () {
+                $('#pageslide > iframe').ready(function () {
+                    NProgress.done();
+                });
+            }, 100);
+        });
+    }
+}
Add a comment
List