[Notice] Announcing the End of Demo Server [Read me]
백기선 2014-07-30
Merge branch 'improve/issue-926-notimail-html-template' of dlab/hive
from pull request 1172
@78b2db1d45dc24e731c32c728fa1864f183560d9
app/assets/stylesheets/less/_yobiUI.less
--- app/assets/stylesheets/less/_yobiUI.less
+++ app/assets/stylesheets/less/_yobiUI.less
@@ -290,14 +290,14 @@
 .fake-file-wrap {
     position: relative; display:block; clear:both;
     overflow: hidden; cursor:pointer;
-    width:70px; /*margin-top:10px;*/
     &:hover {
         background:darken(@white, 10%);
     }
 
     .file {
         position: absolute; z-index:2; cursor:pointer;
-        top:0; left: 5px; /*-15px;*/ width:100px;
+        top:0; left: 5px; /*-15px;*/
+        min-width:100px; width:100%;
         .opacity(0);
     }
 }
app/controllers/UserApp.java
--- app/controllers/UserApp.java
+++ app/controllers/UserApp.java
@@ -42,10 +42,7 @@
 import play.mvc.*;
 import play.mvc.Http.Cookie;
 import utils.*;
-import views.html.user.edit;
-import views.html.user.login;
-import views.html.user.signup;
-import views.html.user.view;
+import views.html.user.*;
 
 import java.util.*;
 
@@ -563,6 +560,52 @@
     }
 
     @With(AnonymousCheckAction.class)
+    public static Result editUserInfoByTabForm(String tabId) {
+        User user = UserApp.currentUser();
+        Form<User> userForm = new Form<>(User.class);
+        userForm = userForm.fill(user);
+
+        switch(UserInfoFormTabType.fromString(tabId)){
+            case PASSWORD:
+                return ok(edit_password.render(userForm, user));
+            case NOTIFICATIONS:
+                return ok(edit_notifications.render(userForm, user));
+            case EMAILS:
+                return ok(edit_emails.render(userForm, user));
+            default:
+            case PROFILE:
+                return ok(edit.render(userForm, user));
+        }
+    }
+
+    private enum UserInfoFormTabType {
+        PROFILE("profile"),
+        PASSWORD("password"),
+        NOTIFICATIONS("notifications"),
+        EMAILS("emails");
+
+        private String tabId;
+
+        UserInfoFormTabType(String tabId) {
+            this.tabId = tabId;
+        }
+
+        public String value(){
+            return tabId;
+        }
+
+        public static UserInfoFormTabType fromString(String text)
+            throws IllegalArgumentException {
+            for(UserInfoFormTabType tab : UserInfoFormTabType.values()){
+                if (tab.value().equalsIgnoreCase(text)) {
+                    return tab;
+                }
+            }
+            throw new IllegalArgumentException("Invalid tabId");
+        }
+    }
+
+    @With(AnonymousCheckAction.class)
     @Transactional
     public static Result editUserInfo() {
         Form<User> userForm = new Form<>(User.class).bindFromRequest("name", "email");
app/controllers/WatchApp.java
--- app/controllers/WatchApp.java
+++ app/controllers/WatchApp.java
@@ -27,7 +27,11 @@
 import models.resource.ResourceParam;
 import play.mvc.Controller;
 import play.mvc.Result;
+import play.i18n.Messages;
 import utils.AccessControl;
+import utils.HttpUtil;
+import utils.RouteUtil;
+import org.apache.commons.lang3.StringUtils;
 
 public class WatchApp extends Controller {
     public static Result watch(ResourceParam resourceParam) {
@@ -57,6 +61,34 @@
 
         Watch.unwatch(user, resource);
 
-        return ok();
+        if (HttpUtil.isJSONPreferred(request())) {
+            return ok();
+        } else {
+            String message = getUnwatchMessage(resource);
+
+            if(!StringUtils.isEmpty(message)) {
+                flash(utils.Constants.SUCCESS, message);
+            }
+
+            return redirect(RouteUtil.getUrl(resource.getType(), resource.getId()));
+        }
+    }
+
+    private static String getUnwatchMessage(Resource resource){
+        switch(resource.getType()) {
+            case ISSUE_POST:
+            case ISSUE_COMMENT:
+                return Messages.get("issue.unwatch.start");
+            case BOARD_POST:
+            case NONISSUE_COMMENT:
+                return Messages.get("post.unwatch.start");
+            case PULL_REQUEST:
+            case REVIEW_COMMENT:
+                return Messages.get("pullRequest.unwatch.start");
+            case PROJECT:
+                return Messages.get("project.unwatch.start");
+            default:
+                return "";
+        }
     }
 }
app/models/NotificationMail.java
--- app/models/NotificationMail.java
+++ app/models/NotificationMail.java
@@ -22,6 +22,7 @@
 
 import info.schleichardt.play2.mailplugin.Mailer;
 import models.enumeration.UserState;
+import models.resource.Resource;
 import org.apache.commons.lang.exception.ExceptionUtils;
 import org.apache.commons.mail.HtmlEmail;
 import org.joda.time.DateTime;
@@ -191,7 +192,7 @@
                 String reference = Url.removeFragment(event.getUrlToView());
 
                 email.setSubject(event.title);
-                email.setHtmlMsg(getHtmlMessage(lang, message, urlToView));
+                email.setHtmlMsg(getHtmlMessage(lang, message, urlToView, event.getResource()));
                 email.setTextMsg(getPlainMessage(lang, message, urlToView));
                 email.setCharset("utf-8");
                 email.addHeader("References", "<" + reference + "@" + Config.getHostname() + ">");
@@ -207,9 +208,21 @@
         }
     }
 
-    private static String getHtmlMessage(Lang lang, String message, String urlToView) {
-        Document doc = Jsoup.parse(Markdown.render(message));
+    private static String getHtmlMessage(Lang lang, String message, String urlToView, Resource resource) {
+        String content = getRenderedHTMLWithTemplate(lang, Markdown.render(message), urlToView, resource);
+        Document doc = Jsoup.parse(content);
 
+        handleLinks(doc);
+        handleImages(doc);
+
+        return doc.html();
+    }
+
+    private static String getRenderedHTMLWithTemplate(Lang lang, String message, String urlToView, Resource resource){
+        return views.html.common.notificationMail.render(lang, message, urlToView, resource).toString();
+    }
+
+    private static void handleLinks(Document doc){
         String[] attrNames = {"src", "href"};
         for (String attrName : attrNames) {
             Elements tags = doc.select("*[" + attrName + "]");
@@ -224,15 +237,6 @@
                 }
             }
         }
-
-        handleImages(doc);
-
-        if (urlToView != null) {
-            doc.body().append(String.format("<hr><a href=\"%s\">%s</a>", urlToView,
-                    Messages.get(lang, "notification.linkToView", utils.Config.getSiteName())));
-        }
-
-        return doc.html();
     }
 
     private static void handleImages(Document doc){
@@ -252,5 +256,4 @@
 
         return msg;
     }
-
 }
 
app/views/common/notificationMail.scala.html (added)
+++ app/views/common/notificationMail.scala.html
@@ -0,0 +1,42 @@
+@**
+* 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.
+**@
+@(userLanguage:Lang, message:String, urlToView:String = null, resource:models.resource.Resource)
+
+@getFooterLinkHTML(link:String, anchorText:String) = {
+  <a href="@link" target="_blank" style="color:#4399e2; text-decoration:underline;">@anchorText</a>
+}
+
+<div style="font-family:'Helvetica Neue','Helvetica','Arial','나눔고딕','NanumGothic','NanumGothicOTF','Apple SD Gothic Neo','맑은 고딕',sans-serif;">
+  @Html(message)
+</div>
+
+<hr style="border:0; border-bottom:1px solid #ddd; margin:20px 0;">
+
+@if(urlToView != null){
+<a href="@urlToView" target="_blank">
+  @Messages("notification.linkToView", utils.Config.getSiteName())
+</a>
+}
+
+<div style="max-width:410px;margin-top:20px;color:#989898;text-align:justify;word-break:break-all;font-size:11px;font-family:'Helvetica Neue','Helvetica','Arial','나눔고딕','NanumGothic','NanumGothicOTF','Apple SD Gothic Neo','맑은 고딕',sans-serif;">
+  @Html(Messages("notification.off.unwatch", getFooterLinkHTML(routes.WatchApp.unwatch(resource.asParameter).toString, Messages("notification.unwatch"))))<br>
+  @Html(Messages("notification.off.settings", getFooterLinkHTML(routes.UserApp.editUserInfoByTabForm("notifications").toString, Messages("userinfo.changeNotifications"))))
+</div>
app/views/user/edit.scala.html
--- app/views/user/edit.scala.html
+++ app/views/user/edit.scala.html
@@ -31,30 +31,61 @@
 </div>
 <div class="page-wrap-outer">
     <div class="page-wrap">
-        <ul id="editUserinfo" class="nav nav-tabs mt20">
-            <li class="active"><a href="#profile" data-toggle="tab">@Messages("userinfo.editProfile")</a></li>
-            <li><a href="#changePassword" data-toggle="tab">@Messages("userinfo.changePassword")</a></li>
-            <li><a href="#watch" data-toggle="tab">@Messages("userinfo.changeNotifications")</a></li>
-            <li><a href="#email" data-toggle="tab">@Messages("userinfo.changeEmails")</a></li>
-        </ul>
+        @partial_edit_tabmenu("profile")
 
-        <div class="tab-content">
-            <div id="profile" class="tab-pane active">
-                @partial_edit_profile(user)
-            </div>
+        <form id="frmBasic" method="post" action="@routes.UserApp.editUserInfo" class="pull-left">
+        <dl>
+          <dt>@Messages("user.name")</dt>
+          <dd class="mt10">
+            <input type="text" name="name" class="text" value="@user.name">
+          </dd>
+          <dt>@Messages("user.email")</dt>
+          <dd class="mt10">
+            <input type="email" name="email" class="text" value="@user.email">
+          </dd>
+          <dd>
+            <button type="submit" class="ybtn ybtn-success">@Messages("userinfo.editProfile")</button>
+          </dd>
+        </dl>
+        </form>
 
-            <div id="email" class="tab-pane">
-                @partial_edit_email(user)
-            </div>
+        <form method="post" action="@routes.UserApp.editUserInfo" class="pull-left" style="margin-left:50px; padding-left:50px; border-left:1px solid #ddd;">
+          <input type="hidden" name="name" value="@user.name">
+          <input type="hidden" name="email" value="@user.email">
 
-            <div id="changePassword" class="tab-pane">
-                @partial_edit_password(user)
+          <div class="avatar-frm">
+            <div class="avatar-wrap xlarge">
+              <img src="@user.avatarUrl" style="width:128px; max-width:none;" />
+              <div class="progress"></div>
             </div>
+            <div class="btn-wrap mt10 center-txt">
+              <div class="ybtn ybtn-small fake-file-wrap btnUploadAvatar">
+                @Messages("userinfo.changeAvatar")<!--
+                --><input id="avatarFile" type="file" class="file" name="filePath" accept="image/*">
+              </div>
+            </div>
+          </div>
+        </form>
 
-            <div id="watch" class="tab-pane">
-                @partial_edit_watch(user)
+        <div id="avatarCropWrap" class="modal hide" role="dialog" data-backdrop="static">
+          <div class="modal-header center-txt">
+            <div class="avatar-wrap xlarge">
+              <img style="width:128px; max-width:none;"/>
             </div>
+          </div>
+          <div class="modal-body">
+            <img style="max-width:500px;">
+            <canvas width="128" height="128" class="hide"></canvas>
+          </div>
+          <div class="modal-footer">
+            <button type="button" class="ybtn ybtn-default" data-dismiss="modal">@Messages("button.cancel")</button>
+            <button type="button" class="ybtn ybtn-success btnSubmitCrop">@Messages("button.save")</button>
+          </div>
         </div>
+
+        <link rel="stylesheet" type="text/css" href="@routes.Assets.at("stylesheets/jcrop/jquery.Jcrop.css")" />
+        <script type="text/javascript" src="@routes.Assets.at("javascripts/lib/jquery/jquery.Jcrop.min.js")"></script>
+        <script type="text/javascript" src="@routes.Assets.at("javascripts/lib/canvas-to-blob.js")"></script>
     </div>
 </div>
 <script type="text/javascript">
 
app/views/user/edit_emails.scala.html (added)
+++ app/views/user/edit_emails.scala.html
@@ -0,0 +1,90 @@
+@**
+* 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.
+**@
+@(userForm:Form[User], user:User)
+
+@import helper._
+@import utils.TemplateHelper._
+@import utils.GravatarUtil._
+
+@siteLayout(user.loginId, utils.MenuType.USER) {
+<div class="site-breadcrumb-outer">
+  <div class="site-breadcrumb-inner">
+    <h3>@Messages("userinfo.accountSetting")</h3>
+  </div>
+</div>
+<div class="page-wrap-outer">
+  <div class="page-wrap">
+    @partial_edit_tabmenu("emails")
+
+    <form action="@routes.UserApp.addEmail" method="post" class="form-inline inner-bubble">
+      <input type="text" placeholder="@Messages("user.email.new")" name="email" class="text uname">
+      <button type="submit" class="ybtn ybtn-success">@Messages("button.add")</button>
+    </form>
+
+    <hr>
+
+    <p>
+      @Messages("emails.main.email.descr")<br>
+      @Messages("emails.sub.email.descr")
+    </p>
+
+    <table class="table mt20">
+      <tr>
+        <td>
+          <img src="@getAvatar(user.email, 40)" width="40" height="40">
+          <strong class="ml10">@user.email</strong>
+          <span class="label-head vmiddle ml10">@Messages("emails.main.email")</span>
+        </td>
+        <td style="text-align:right;">
+        </td>
+      </tr>
+
+      @for(mail <- user.emails){
+      <tr>
+        <td>
+          <img src="@getAvatar(mail.email, 40)" width="40" height="40">
+          <span class="ml10">@mail.email</span>
+        </td>
+        <td style="text-align:right; vertical-align: middle;">
+          <button type="button" data-request-method="delete" data-request-uri="@routes.UserApp.deleteEmail(mail.id)" class="ybtn ybtn-small ybtn-danger">
+            @Messages("button.delete")
+          </button>
+          @if(mail.valid) {
+          <button type="button" data-request-method="put" href="@routes.UserApp.setAsMainEmail(mail.id)" class="ybtn ybtn-small" style="width:150px;">
+            @Messages("emails.set.as.main")
+          </button>
+          } else {
+          <button type="button" data-request-method="post" href="@routes.UserApp.sendValidationEmail(mail.id)" class="ybtn ybtn-small" style="width:150px;">
+            <i class="yobicon-error2 orange-txt mr5" style="vertical-align: bottom;"></i>@Messages("emails.send.validatino.mail")
+          </button>
+          }
+        </td>
+      </tr>
+      }
+    </table>
+  </div>
+</div>
+<script type="text/javascript">
+    $(function(){
+        $yobi.loadModule("user.Setting");
+    });
+</script>
+}
 
app/views/user/edit_notifications.scala.html (added)
+++ app/views/user/edit_notifications.scala.html
@@ -0,0 +1,82 @@
+@**
+* 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.
+**@
+@(userForm:Form[User], user:User)
+
+@import helper._
+@import utils.TemplateHelper._
+
+@siteLayout(user.loginId, utils.MenuType.USER) {
+<div class="site-breadcrumb-outer">
+  <div class="site-breadcrumb-inner">
+    <h3>@Messages("userinfo.accountSetting")</h3>
+  </div>
+</div>
+<div class="page-wrap-outer">
+  <div class="page-wrap">
+    @partial_edit_tabmenu("notifications")
+
+    <div>
+      @defining(UserProjectNotification.getProjectNotifications(user)) { notiMap =>
+      <ul id="notification-projects" class="unstyled lst-stacked span3 mr20">
+        @defining(Watch.findBy(user, ResourceType.PROJECT)) { watches =>
+          @if(watches.size > 0) {
+            @for(i <- 0 until watches.size) {
+              @defining(Project.find.byId(watches.get(i).resourceId.toLong)) { project =>
+                <li @if(i == 0){class="active"}><a href="#@project.id" data-toggle="tab">@project.owner / @project.name</a></li>
+              }
+            }
+          }
+        }
+      </ul>
+      <div class="tab-content">
+        @defining(Watch.findBy(user, ResourceType.PROJECT)) { watches =>
+          @if(watches.size > 0) {
+            @for(i <- 0 until watches.size) {
+              @defining(Project.find.byId(watches.get(i).resourceId.toLong)) { project =>
+                <div id="@project.id" class="tab-pane @if(i == 0){active}">
+                  <table class="table table-striped table-bordered">
+                    @for(notiType <- models.enumeration.EventType.notiTypes) {
+                    <tr>
+                      <th>@notiType.getDescr</th>
+                      <td>
+                        <div class="switch" data-on-label="On" data-off-label="Off">
+                          <input class="notiUpdate" data-href="@routes.WatchProjectApp.toggle(project.id, notiType.name())" type="checkbox" data-toggle="switch" @if(UserProjectNotification.isEnabledNotiType(notiMap, project, notiType)){ checked="checked" }>
+                        </div>
+                      </td>
+                    </tr>
+                    }
+                  </table>
+                </div>
+              }
+            }
+          }
+        }
+      </div>
+      }
+    </div>
+  </div>
+</div>
+<script type="text/javascript">
+    $(function(){
+        $yobi.loadModule("user.Setting");
+    });
+</script>
+}
 
app/views/user/edit_password.scala.html (added)
+++ app/views/user/edit_password.scala.html
@@ -0,0 +1,63 @@
+@**
+* 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.
+**@
+@(userForm:Form[User], user:User)
+
+@import helper._
+@import utils.TemplateHelper._
+
+@siteLayout(user.loginId, utils.MenuType.USER) {
+<div class="site-breadcrumb-outer">
+  <div class="site-breadcrumb-inner">
+    <h3>@Messages("userinfo.accountSetting")</h3>
+  </div>
+</div>
+<div class="page-wrap-outer">
+  <div class="page-wrap">
+    @partial_edit_tabmenu("password")
+
+    <form id="frmPassword" method="post" action="@routes.UserApp.resetUserPassword">
+      <input type="hidden" name="loginId" value="@user.loginId" />
+      <dl>
+        <dt>@Messages("user.currentPassword")</dt>
+        <dd class="mt10">
+          <input type="password" id="oldPassword" name="oldPassword" value="" autocomplete="off" />
+        </dd>
+        <dt>@Messages("user.newPassword")</dt>
+        <dd class="mt10">
+          <input type="password" id="password" name="password" value="" autocomplete="off" />
+        </dd>
+        <dt>@Messages("validation.retypePassword")</dt>
+        <dd class="mt10">
+          <input type="password" id="retypedPassword" name="retypedPassword" value="" autocomplete="off" />
+        </dd>
+        <dd>
+          <button type="submit" class="ybtn ybtn-success">@Messages("userinfo.changePassword")</button>
+        </dd>
+      </dl>
+    </form>
+  </div>
+</div>
+<script type="text/javascript">
+    $(function(){
+        $yobi.loadModule("user.Setting");
+    });
+</script>
+}
 
app/views/user/partial_edit_email.scala.html (deleted)
--- app/views/user/partial_edit_email.scala.html
@@ -1,70 +0,0 @@
-@**
-* 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.
-**@
-@(user:User)
-
-@import utils.GravatarUtil._
-
-<form action="@routes.UserApp.addEmail" method="post" class="form-inline inner-bubble">
-  <input type="text" placeholder="@Messages("user.email.new")" name="email" class="text uname">
-  <button type="submit" class="ybtn ybtn-success">@Messages("button.add")</button>
-</form>
-
-<hr>
-
-<p>
-  @Messages("emails.main.email.descr")<br>
-  @Messages("emails.sub.email.descr")
-</p>
-
-<table class="table mt20">
-  <tr>
-    <td>
-      <img src="@getAvatar(user.email, 40)" width="40" height="40">
-      <strong class="ml10">@user.email</strong>
-      <span class="label-head vmiddle ml10">@Messages("emails.main.email")</span>
-    </td>
-    <td style="text-align:right;">
-    </td>
-  </tr>
-
-  @for(mail <- user.emails){
-  <tr>
-    <td>
-      <img src="@getAvatar(mail.email, 40)" width="40" height="40">
-      <span class="ml10">@mail.email</span>
-    </td>
-    <td style="text-align:right; vertical-align: middle;">
-      <button type="button" data-request-method="delete" data-request-uri="@routes.UserApp.deleteEmail(mail.id)" class="ybtn ybtn-small ybtn-danger">
-        @Messages("button.delete")
-      </button>
-      @if(mail.valid) {
-      <button type="button" data-request-method="put" href="@routes.UserApp.setAsMainEmail(mail.id)" class="ybtn ybtn-small" style="width:150px;">
-        @Messages("emails.set.as.main")
-      </button>
-      } else {
-      <button type="button" data-request-method="post" href="@routes.UserApp.sendValidationEmail(mail.id)" class="ybtn ybtn-small" style="width:150px;">
-        <i class="yobicon-error2 orange-txt mr5" style="vertical-align: bottom;"></i>@Messages("emails.send.validatino.mail")
-      </button>
-      }
-    </td>
-  </tr>
-  }
-</table>
 
app/views/user/partial_edit_password.scala.html (deleted)
--- app/views/user/partial_edit_password.scala.html
@@ -1,42 +0,0 @@
-@**
-* 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.
-**@
-@(user:User)
-
-<form id="frmPassword" method="post" action="@routes.UserApp.resetUserPassword">
-  <input type="hidden" name="loginId" value="@user.loginId" />
-  <dl>
-    <dt>@Messages("user.currentPassword")</dt>
-    <dd class="mt10">
-      <input type="password" id="oldPassword" name="oldPassword" value="" autocomplete="off" />
-    </dd>
-    <dt>@Messages("user.newPassword")</dt>
-    <dd class="mt10">
-      <input type="password" id="password" name="password" value="" autocomplete="off" />
-    </dd>
-    <dt>@Messages("validation.retypePassword")</dt>
-    <dd class="mt10">
-      <input type="password" id="retypedPassword" name="retypedPassword" value="" autocomplete="off" />
-    </dd>
-    <dd>
-      <button type="submit" class="ybtn ybtn-success">@Messages("userinfo.changePassword")</button>
-    </dd>
-  </dl>
-</form>
 
app/views/user/partial_edit_profile.scala.html (deleted)
--- app/views/user/partial_edit_profile.scala.html
@@ -1,75 +0,0 @@
-@**
-* 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.
-**@
-@(user:User)
-
-<form id="frmBasic" method="post" action="@routes.UserApp.editUserInfo" class="pull-left">
-<dl>
-  <dt>@Messages("user.name")</dt>
-  <dd class="mt10">
-    <input type="text" name="name" class="text" value="@user.name">
-  </dd>
-  <dt>@Messages("user.email")</dt>
-  <dd class="mt10">
-    <input type="email" name="email" class="text" value="@user.email">
-  </dd>
-  <dd>
-    <button type="submit" class="ybtn ybtn-success">@Messages("userinfo.editProfile")</button>
-  </dd>
-</dl>
-</form>
-
-<form method="post" action="@routes.UserApp.editUserInfo" class="pull-left" style="margin-left:50px; padding-left:50px; border-left:1px solid #ddd;">
-  <input type="hidden" name="name" value="@user.name">
-  <input type="hidden" name="email" value="@user.email">
-
-  <div class="avatar-frm">
-    <div class="avatar-wrap xlarge">
-      <img src="@user.avatarUrl" style="width:128px; max-width:none;" />
-      <div class="progress"></div>
-    </div>
-    <div class="btn-wrap mt10 center-txt">
-      <div class="ybtn ybtn-small fake-file-wrap btnUploadAvatar">
-        @Messages("userinfo.changeAvatar")<!--
-        --><input id="avatarFile" type="file" class="file" name="filePath" accept="image/*">
-      </div>
-    </div>
-  </div>
-</form>
-
-<div id="avatarCropWrap" class="modal hide" role="dialog" data-backdrop="static">
-  <div class="modal-header center-txt">
-    <div class="avatar-wrap xlarge">
-      <img style="width:128px; max-width:none;"/>
-    </div>
-  </div>
-  <div class="modal-body">
-    <img style="max-width:500px;">
-    <canvas width="128" height="128" class="hide"></canvas>
-  </div>
-  <div class="modal-footer">
-    <button type="button" class="ybtn ybtn-default" data-dismiss="modal">@Messages("button.cancel")</button>
-    <button type="button" class="ybtn ybtn-success btnSubmitCrop">@Messages("button.save")</button>
-  </div>
-</div>
-
-<link rel="stylesheet" type="text/css" href="@routes.Assets.at("stylesheets/jcrop/jquery.Jcrop.css")" />
-<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/jquery/jquery.Jcrop.min.js")"></script>
-<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/canvas-to-blob.js")"></script>
 
app/views/user/partial_edit_tabmenu.scala.html (added)
+++ app/views/user/partial_edit_tabmenu.scala.html
@@ -0,0 +1,43 @@
+@**
+* 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.
+**@
+@(tabId:String = "profile")
+<ul class="nav nav-tabs mt20">
+  <li @if(tabId == "profile"){ class="active" }>
+    <a href="@routes.UserApp.editUserInfoForm">
+      @Messages("userinfo.editProfile")
+    </a>
+  </li>
+  <li @if(tabId == "password"){ class="active" }>
+    <a href="@routes.UserApp.editUserInfoByTabForm("password")">
+    @Messages("userinfo.changePassword")
+    </a>
+  </li>
+  <li @if(tabId == "notifications"){ class="active" }>
+    <a href="@routes.UserApp.editUserInfoByTabForm("notifications")">
+    @Messages("userinfo.changeNotifications")
+    </a>
+  </li>
+  <li @if(tabId == "emails"){ class="active" }>
+    <a href="@routes.UserApp.editUserInfoByTabForm("emails")">
+    @Messages("userinfo.changeEmails")
+    </a>
+  </li>
+</ul>
 
app/views/user/partial_edit_watch.scala.html (deleted)
--- app/views/user/partial_edit_watch.scala.html
@@ -1,61 +0,0 @@
-@**
-* 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.
-**@
-@(user:User)
-
-@import utils.TemplateHelper._
-
-@defining(UserProjectNotification.getProjectNotifications(user)) { notiMap =>
-<ul class="unstyled lst-stacked span3" style="margin-right: 20px;">
-  @defining(Watch.findBy(user, ResourceType.PROJECT)) { watches =>
-    @if(watches.size > 0) {
-      @for(i <- 0 until watches.size) {
-        @defining(Project.find.byId(watches.get(i).resourceId.toLong)) { project =>
-          <li @if(i == 0){class="active"}><a href="#@project.id" data-toggle="tab">@project.owner / @project.name</a></li>
-        }
-      }
-    }
-  }
-</ul>
-<div class="tab-content">
-  @defining(Watch.findBy(user, ResourceType.PROJECT)) { watches =>
-    @if(watches.size > 0) {
-      @for(i <- 0 until watches.size) {
-        @defining(Project.find.byId(watches.get(i).resourceId.toLong)) { project =>
-          <div class="tab-pane @if(i == 0){active}" id="@project.id">
-            <table class="table table-striped table-bordered">
-              @for(notiType <- models.enumeration.EventType.notiTypes) {
-              <tr>
-                <th>@notiType.getDescr</th>
-                <td>
-                  <div class="switch" data-on-label="On" data-off-label="Off">
-                    <input class="notiUpdate" data-href="@routes.WatchProjectApp.toggle(project.id, notiType.name())" type="checkbox" data-toggle="switch" @if(UserProjectNotification.isEnabledNotiType(notiMap, project, notiType)){ checked="checked" }>
-                  </div>
-                </td>
-              </tr>
-              }
-            </table>
-          </div>
-        }
-      }
-    }
-  }
-</div>
-}
conf/messages
--- conf/messages
+++ conf/messages
@@ -323,6 +323,8 @@
 notification.member.request.cancel.title = [{0}] {1} cancels the request to be a member.
 notification.member.request.title = [{0}] {1} wants to join your project
 notification.none = No notification has been received.
+notification.off.unwatch = You can {0} or
+notification.off.settings = change settings at {0} if you want mute this.
 notification.organization.member.enroll.accept = Joined as a member.
 notification.organization.member.enroll.cancel = The request for joining your group has been canceled.
 notification.organization.member.enroll.request = Received a request for joining your group.
@@ -358,6 +360,7 @@
 notification.type.pullrequest.state.changed = Pull request Status changed.
 notification.type.pullrequest.unreviewed = Pull request review is canceled.
 notification.type.review.state.changed = Review Thread State Change
+notification.unwatch = Unwatch
 notification.watch = Watch
 notification.will.help = When you watch project, you will receive notifications.
 organization.create = Create Group
@@ -541,6 +544,7 @@
 project.transfer.requestion = Do you want to transfer this project?
 project.transfer.this = Transfer this project
 project.unwatch = Unwatch
+project.unwatch.start = Notifications of this project has muted
 project.vcs = Repository type
 project.watch = Watch
 project.watching = Watching
conf/messages.ko
--- conf/messages.ko
+++ conf/messages.ko
@@ -323,6 +323,8 @@
 notification.member.request.cancel.title = [{0}] {1} 님이 멤버 요청을 취소하였습니다.
 notification.member.request.title = [{0}] {1} 님이 멤버 요청을 보냈습니다.
 notification.none = 알림 메시지가 없습니다.
+notification.off.unwatch = {0}를 눌러 더 이상 알림을 받지 않거나,
+notification.off.settings = 알림 수신 여부를 {0}에서 수정하실 수 있습니다.
 notification.organization.member.enroll.accept = 멤버로 등록됨
 notification.organization.member.enroll.cancel = 멤버 등록 요청이 취소됨
 notification.organization.member.enroll.request = 멤버 등록 요청을 받음
@@ -358,6 +360,7 @@
 notification.type.pullrequest.state.changed = 코드보내기 상태 변경
 notification.type.pullrequest.unreviewed = 코드보내기 리뷰가 취소되었습니다.
 notification.type.review.state.changed = 리뷰 스레드 상태 변경
+notification.unwatch = 그만 지켜보기
 notification.watch = 지켜보기
 notification.will.help = 프로젝트를 지켜보면 다음 이벤트가 발생할 때 알림 메시지를 받습니다.
 organization.create = 그룹 만들기
@@ -541,6 +544,7 @@
 project.transfer.requestion = 프로젝트를 이관하시겠습니까?
 project.transfer.this = 프로젝트를 이관합니다.
 project.unwatch = 그만 지켜보기
+project.unwatch.start = 프로젝트를 더 이상 지켜보지 않습니다
 project.vcs = 코드 관리 시스템
 project.watch = 지켜보기
 project.watching = 지켜보기 중
conf/routes
--- conf/routes
+++ conf/routes
@@ -50,6 +50,7 @@
 GET            /users/signupform                                                      controllers.UserApp.signupForm()
 POST           /users/signup                                                          controllers.UserApp.newUser()
 GET            /user/editform                                                         controllers.UserApp.editUserInfoForm()
+GET            /user/editform/:tabId                                                  controllers.UserApp.editUserInfoByTabForm(tabId: String)
 POST           /user/edit                                                             controllers.UserApp.editUserInfo()
 POST           /user/resetPassword                                                    controllers.UserApp.resetUserPassword()
 GET            /user/isUsed                                                           controllers.UserApp.isUsed(name:String)
@@ -241,6 +242,7 @@
 # Watch
 POST           /watch                                                                 controllers.WatchApp.watch(resource: models.resource.ResourceParam)
 POST           /unwatch                                                               controllers.WatchApp.unwatch(resource: models.resource.ResourceParam)
+GET            /unwatch                                                               controllers.WatchApp.unwatch(resource: models.resource.ResourceParam)
 
 # Statistics
 GET            /:user/:project/statistics                                             controllers.StatisticsApp.statistics(user, project)
public/javascripts/service/yobi.user.Setting.js
--- public/javascripts/service/yobi.user.Setting.js
+++ public/javascripts/service/yobi.user.Setting.js
@@ -37,6 +37,7 @@
 
             _initFormValidator();
             _initAvatarUploader();
+            _showNotificationTab();
         }
 
         /**
@@ -396,6 +397,10 @@
             })
         }
 
+        function _showNotificationTab(){
+            $('#notification-projects a[href="' + location.hash + '"]').tab("show");
+        }
+
         _init(htOptions || {});
     };
 })("yobi.user.Setting");
Add a comment
List