Wansoon Park 2014-04-24
Mention: Changes mention.js to at.js
@e7345e8921a1cea51fc10940799d4a3de9a1fdf6
app/controllers/ProjectApp.java
--- app/controllers/ProjectApp.java
+++ app/controllers/ProjectApp.java
@@ -500,11 +500,9 @@
         collectAuthorAndCommenter(project, number, userList, resourceType);
         addProjectMemberList(project, userList);
         userList.remove(UserApp.currentUser());
-        List<Issue> issueList = getMentionIssueList(project);
 
         List<Map<String, String>> mentionList = new ArrayList<>();
         collectedUsersToMap(mentionList, userList);
-        collectedIssuesToMap(mentionList, issueList);
         return ok(toJson(mentionList));
     }
 
@@ -862,8 +860,9 @@
         for(User user: userList) {
             Map<String, String> projectUserMap = new HashMap<>();
             if(!user.loginId.equals(Constants.ADMIN_LOGIN_ID) && user != null){
-                projectUserMap.put("username", user.loginId);
-                projectUserMap.put("name", user.name);
+                projectUserMap.put("loginid", user.loginId);
+                projectUserMap.put("username", user.name);
+                projectUserMap.put("name", user.name + user.loginId);
                 projectUserMap.put("image", user.avatarUrl());
                 users.add(projectUserMap);
             }
app/views/board/create.scala.html
--- app/views/board/create.scala.html
+++ app/views/board/create.scala.html
@@ -78,8 +78,11 @@
 
 @common.markdown(project)
 
-<link rel="stylesheet" type="text/css" media="screen" href="@routes.Assets.at("javascripts/lib/mentionjs/mention.css")">
-<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/mentionjs/mention.js")"></script>
+<link rel="stylesheet" type="text/css" media="screen" href="@routes.Assets.at("javascripts/lib/atjs/jquery.atwho.css")">
+<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.caret.min.js")"></script>
+<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.atwho.js")"></script>
+<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.browser.js")"></script>
+<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.firefox.keyfix.js")"></script>
 <script type="text/javascript">
 $(document).ready(function(){
 	$yobi.loadModule("board.Write", {
app/views/board/edit.scala.html
--- app/views/board/edit.scala.html
+++ app/views/board/edit.scala.html
@@ -79,8 +79,11 @@
 </div>
 @common.markdown(project)
 
-<link rel="stylesheet" type="text/css" media="screen" href="@routes.Assets.at("javascripts/lib/mentionjs/mention.css")">
-<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/mentionjs/mention.js")"></script>
+<link rel="stylesheet" type="text/css" media="screen" href="@routes.Assets.at("javascripts/lib/atjs/jquery.atwho.css")">
+<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.caret.min.js")"></script>
+<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.atwho.js")"></script>
+<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.browser.js")"></script>
+<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.firefox.keyfix.js")"></script>
 <script type="text/javascript">
 $(document).ready(function(){
 	$yobi.loadModule("board.Write", {
app/views/board/view.scala.html
--- app/views/board/view.scala.html
+++ app/views/board/view.scala.html
@@ -116,8 +116,11 @@
 @common.markdown(project)
 @common.commentDeleteModal()
 
-<link rel="stylesheet" type="text/css" media="screen" href="@routes.Assets.at("javascripts/lib/mentionjs/mention.css")">
-<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/mentionjs/mention.js")"></script>
+<link rel="stylesheet" type="text/css" media="screen" href="@routes.Assets.at("javascripts/lib/atjs/jquery.atwho.css")">
+<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.caret.min.js")"></script>
+<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.atwho.js")"></script>
+<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.browser.js")"></script>
+<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.firefox.keyfix.js")"></script>
 <script type="text/javascript">
 	$(document).ready(function(){
 		$yobi.loadModule("board.View", {
app/views/code/diff.scala.html
--- app/views/code/diff.scala.html
+++ app/views/code/diff.scala.html
@@ -201,8 +201,11 @@
 @common.mergely()
 @common.commentDeleteModal("#code-browse-wrap")
 
-<link rel="stylesheet" type="text/css" media="screen" href="@routes.Assets.at("javascripts/lib/mentionjs/mention.css")">
-<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/mentionjs/mention.js")"></script>
+<link rel="stylesheet" type="text/css" media="screen" href="@routes.Assets.at("javascripts/lib/atjs/jquery.atwho.css")">
+<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.caret.min.js")"></script>
+<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.atwho.js")"></script>
+<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.browser.js")"></script>
+<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.firefox.keyfix.js")"></script>
 <script type="text/javascript" src="@routes.Assets.at("javascripts/common/yobi.CodeCommentBlock.js")"></script>
 <script type="text/javascript" src="@routes.Assets.at("javascripts/common/yobi.CodeCommentBox.js")"></script>
 <script type="text/javascript" src="@routes.Assets.at("javascripts/lib/jquery/jquery-ui-1.10.4.custom.min.js")"></script>
@@ -219,6 +222,11 @@
             target:'comment-editor',
             url : "@Html(routes.ProjectApp.mentionListAtCommitDiff(project.owner, project.name, commit.getId).toString)"
         });
+        
+        yobi.Mention({
+            target:'review-editor',
+            url : "@Html(routes.ProjectApp.mentionListAtCommitDiff(project.owner, project.name, commit.getId).toString)"
+        });
     });
 </script>
 }
app/views/code/svnDiff.scala.html
--- app/views/code/svnDiff.scala.html
+++ app/views/code/svnDiff.scala.html
@@ -178,8 +178,11 @@
 
 <link rel="stylesheet" type="text/css" media="screen" href="@routes.Assets.at("javascripts/lib/mergely/codemirror.css")">
 <link rel="stylesheet" type="text/css" media="screen" href="@routes.Assets.at("javascripts/lib/mergely/mergely.css")">
-<link rel="stylesheet" type="text/css" media="screen" href="@routes.Assets.at("javascripts/lib/mentionjs/mention.css")">
-<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/mentionjs/mention.js")"></script>
+<link rel="stylesheet" type="text/css" media="screen" href="@routes.Assets.at("javascripts/lib/atjs/jquery.atwho.css")">
+<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.caret.min.js")"></script>
+<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.atwho.js")"></script>
+<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.browser.js")"></script>
+<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.firefox.keyfix.js")"></script>
 <script type="text/javascript" src="@routes.Assets.at("javascripts/lib/diff.js")"></script>
 <script type="text/javascript" src="@routes.Assets.at("javascripts/lib/mergely/codemirror.min.js")"></script>
 <script type="text/javascript" src="@routes.Assets.at("javascripts/lib/mergely/mergely.js")"></script>
app/views/common/reviewForm.scala.html
--- app/views/common/reviewForm.scala.html
+++ app/views/common/reviewForm.scala.html
@@ -21,6 +21,7 @@
 @(project:Project, resourceType:ResourceType, action:String)
 
 @import models.enumeration.ResourceType
+@import scala.collection.Map
 
 <div id="review-form" class="review-form">
     <form action="@action" method="post" enctype="multipart/form-data">
@@ -38,7 +39,7 @@
         </div>
         <div class="write-comment-box nm">
             <div class="write-comment-wrap">
-                @common.editor("contents")
+                @common.editor("contents", "", Map("id"->"review-editor"))
 
                 @** fileUploader **@
                 @if(!UserApp.currentUser.isAnonymous){
app/views/git/create.scala.html
--- app/views/git/create.scala.html
+++ app/views/git/create.scala.html
@@ -131,8 +131,11 @@
         </div>
     </div>
 </div>
-<link rel="stylesheet" type="text/css" media="screen" href="@routes.Assets.at("javascripts/lib/mentionjs/mention.css")">
-<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/mentionjs/mention.js")"></script>
+<link rel="stylesheet" type="text/css" media="screen" href="@routes.Assets.at("javascripts/lib/atjs/jquery.atwho.css")">
+<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.caret.min.js")"></script>
+<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.atwho.js")"></script>
+<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.browser.js")"></script>
+<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.firefox.keyfix.js")"></script>
 <script type="text/javascript">
 $(document).ready(function() {
     $yobi.loadModule("git.Write", {
app/views/git/edit.scala.html
--- app/views/git/edit.scala.html
+++ app/views/git/edit.scala.html
@@ -132,8 +132,11 @@
 </div>
 @common.markdown(project)
 
-<link rel="stylesheet" type="text/css" media="screen" href="@routes.Assets.at("javascripts/lib/mentionjs/mention.css")">
-<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/mentionjs/mention.js")"></script>
+<link rel="stylesheet" type="text/css" media="screen" href="@routes.Assets.at("javascripts/lib/atjs/jquery.atwho.css")">
+<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.caret.min.js")"></script>
+<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.atwho.js")"></script>
+<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.browser.js")"></script>
+<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.firefox.keyfix.js")"></script>
 <script type="text/javascript">
 	$(document).ready(function(){
         $yobi.loadModule("git.Write");
app/views/git/view.scala.html
--- app/views/git/view.scala.html
+++ app/views/git/view.scala.html
@@ -123,8 +123,11 @@
 @common.mergely()
 @common.commentDeleteModal()
 
-<link rel="stylesheet" type="text/css" media="screen" href="@routes.Assets.at("javascripts/lib/mentionjs/mention.css")">
-<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/mentionjs/mention.js")"></script>
+<link rel="stylesheet" type="text/css" media="screen" href="@routes.Assets.at("javascripts/lib/atjs/jquery.atwho.css")">
+<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.caret.min.js")"></script>
+<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.atwho.js")"></script>
+<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.browser.js")"></script>
+<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.firefox.keyfix.js")"></script>
 <script type="text/javascript" src="@routes.Assets.at("javascripts/lib/diff.js")"></script>
 <script type="text/javascript" src="@routes.Assets.at("javascripts/common/yobi.Interval.js")"></script>
 <script type="text/javascript" src="@routes.Assets.at("javascripts/common/yobi.CodeCommentBox.js")"></script>
app/views/git/viewChanges.scala.html
--- app/views/git/viewChanges.scala.html
+++ app/views/git/viewChanges.scala.html
@@ -198,8 +198,11 @@
 @common.mergely()
 @common.commentDeleteModal("#changes")
 
-<link rel="stylesheet" type="text/css" media="screen" href="@routes.Assets.at("javascripts/lib/mentionjs/mention.css")">
-<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/mentionjs/mention.js")"></script>
+<link rel="stylesheet" type="text/css" media="screen" href="@routes.Assets.at("javascripts/lib/atjs/jquery.atwho.css")">
+<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.caret.min.js")"></script>
+<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.atwho.js")"></script>
+<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.browser.js")"></script>
+<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.firefox.keyfix.js")"></script>
 <script type="text/javascript" src="@routes.Assets.at("javascripts/common/yobi.CodeCommentBox.js")"></script>
 <script type="text/javascript" src="@routes.Assets.at("javascripts/common/yobi.CodeCommentBlock.js")"></script>
 <script type="text/javascript" src="@routes.Assets.at("javascripts/lib/jquery/jquery-ui-1.10.4.custom.min.js")"></script>
@@ -216,6 +219,15 @@
             }
         });
 
+        yobi.Mention({
+            target:'review-editor',
+            @if(pull.getCurrentCommits.isEmpty) {
+                url : "@Html(routes.ProjectApp.mentionListAtPullRequest(pull.toProject.owner, pull.toProject.name, "", pull.id).toString())"
+            } else {
+                url : "@Html(routes.ProjectApp.mentionListAtPullRequest(pull.toProject.owner, pull.toProject.name, pull.getCurrentCommits.get(0).getCommitId, pull.id).toString())"
+            }
+        });
+
         // code.Diff
         $yobi.loadModule("code.Diff", {
             "bCommentable" : @isProjectResourceCreatable(UserApp.currentUser, project, ResourceType.REVIEW_COMMENT)
app/views/issue/create.scala.html
--- app/views/issue/create.scala.html
+++ app/views/issue/create.scala.html
@@ -144,9 +144,9 @@
 @common.markdown(project)
 @common.issueLabel()
 @common.select2()
-
-<link rel="stylesheet" type="text/css" media="screen" href="@routes.Assets.at("javascripts/lib/mentionjs/mention.css")">
-<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/mentionjs/mention.js")"></script>
+<link rel="stylesheet" type="text/css" media="screen" href="@routes.Assets.at("javascripts/lib/atjs/jquery.atwho.css")">
+<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.caret.min.js")"></script>
+<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.atwho.js")"></script>
 <script type="text/javascript">
 	$(document).ready(function(){
 	    // yobi.Label
app/views/issue/edit.scala.html
--- app/views/issue/edit.scala.html
+++ app/views/issue/edit.scala.html
@@ -29,6 +29,7 @@
 
 @projectLayout(Messages(title), project, utils.MenuType.ISSUE) {
 @projectMenu(project, utils.MenuType.ISSUE, "main-menu-only")
+
 <div class="page-wrap-outer">
     <div class="project-page-wrap">
         <div class="content-wrap frm-wrap">
@@ -185,8 +186,11 @@
 @common.markdown(project)
 @common.issueLabel()
 @common.select2()
-<link rel="stylesheet" type="text/css" media="screen" href="@routes.Assets.at("javascripts/lib/mentionjs/mention.css")">
-<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/mentionjs/mention.js")"></script>
+<link rel="stylesheet" type="text/css" media="screen" href="@routes.Assets.at("javascripts/lib/atjs/jquery.atwho.css")">
+<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.caret.min.js")"></script>
+<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.atwho.js")"></script>
+<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.browser.js")"></script>
+<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.firefox.keyfix.js")"></script>
 <script type="text/javascript">
     $(document).ready(function(){
         // yobi.Label
app/views/issue/view.scala.html
--- app/views/issue/view.scala.html
+++ app/views/issue/view.scala.html
@@ -331,8 +331,11 @@
 @common.select2()
 
 <link rel="stylesheet" type="text/css" media="screen" href="@routes.IssueLabelApp.labelStyles(project.owner, project.name)">
-<link rel="stylesheet" type="text/css" media="screen" href="@routes.Assets.at("javascripts/lib/mentionjs/mention.css")">
-<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/mentionjs/mention.js")"></script>
+<link rel="stylesheet" type="text/css" media="screen" href="@routes.Assets.at("javascripts/lib/atjs/jquery.atwho.css")">
+<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.caret.min.js")"></script>
+<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.atwho.js")"></script>
+<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.browser.js")"></script>
+<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.firefox.keyfix.js")"></script>
 <script type="text/javascript">
     $(document).ready(function(){
         // yobi.issue.View
app/views/milestone/create.scala.html
--- app/views/milestone/create.scala.html
+++ app/views/milestone/create.scala.html
@@ -73,9 +73,11 @@
 </div>
 @common.markdown(project)
 
-<link rel="stylesheet" type="text/css" media="screen" href="@routes.Assets.at("javascripts/lib/mentionjs/mention.css")">
-<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/mentionjs/mention.js")"></script>
-
+<link rel="stylesheet" type="text/css" media="screen" href="@routes.Assets.at("javascripts/lib/atjs/jquery.atwho.css")">
+<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.caret.min.js")"></script>
+<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.atwho.js")"></script>
+<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.browser.js")"></script>
+<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.firefox.keyfix.js")"></script>
 <link rel="stylesheet" type="text/css" href="@routes.Assets.at("javascripts/lib/pikaday/pikaday.css")" />
 <script type="text/javascript" src="@routes.Assets.at("javascripts/lib/moment-with-langs.min.js")"></script>
 <script type="text/javascript" src="@routes.Assets.at("javascripts/lib/pikaday/pikaday.js")"></script>
app/views/milestone/edit.scala.html
--- app/views/milestone/edit.scala.html
+++ app/views/milestone/edit.scala.html
@@ -77,9 +77,11 @@
 </div>
 @common.markdown(project)
 
-<link rel="stylesheet" type="text/css" media="screen" href="@routes.Assets.at("javascripts/lib/mentionjs/mention.css")">
-<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/mentionjs/mention.js")"></script>
-
+<link rel="stylesheet" type="text/css" media="screen" href="@routes.Assets.at("javascripts/lib/atjs/jquery.atwho.css")">
+<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.caret.min.js")"></script>
+<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.atwho.js")"></script>
+<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.browser.js")"></script>
+<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.firefox.keyfix.js")"></script>
 <link rel="stylesheet" type="text/css" href="@routes.Assets.at("javascripts/lib/pikaday/pikaday.css")" />
 <script type="text/javascript" src="@routes.Assets.at("javascripts/lib/moment-with-langs.min.js")"></script>
 <script type="text/javascript" src="@routes.Assets.at("javascripts/lib/pikaday/pikaday.js")"></script>
public/javascripts/common/yobi.Mention.js
--- public/javascripts/common/yobi.Mention.js
+++ public/javascripts/common/yobi.Mention.js
@@ -29,15 +29,12 @@
      */
     function _initVar(htOptions) {
         htVar = htOptions || {}; // set htVar as htOptions
-
-        htVar.htMentionList = {
-            "emptyQuery": true,
-            "typeaheadOpts": {
-                "items": 15
-            },
-            "users": [],
-            "queryBy": ["username", "name"]
-        };
+        htVar.atConfig = {
+            at: "@",
+            data: [],
+            tpl: "<li data-value='@${loginid}'><img style='width:20px;height:20px;' src='${image}'> ${username} <small>${loginid}</small></li>",
+            show_the_at: true
+        }
     }
 
     /**
@@ -62,8 +59,8 @@
     function _onKeyInput(eEvt){
         eEvt = eEvt || window.event;
 
-        if(eEvt.which === 64 || eEvt.which === 35){ // 64 = @ or 35 = #
-            if(htVar.htMentionList.users.length == 0) {
+        if(eEvt.which === 64) { // 64 = @
+            if(htVar.atConfig.data.length == 0) {
                 _findMentionList();
             }
         }
@@ -82,8 +79,11 @@
     }
 
     function _onLoadUserList(aData){
-        htVar.htMentionList.users = aData;
-        htElement.welTarget.mention(htVar.htMentionList);
+        htVar.atConfig.data = aData;
+
+        $inputor = htElement.welTarget.atwho(htVar.atConfig);
+        $inputor.caret("pos", 47);
+        $inputor.focus().atwho("run");
     }
 
     _init(htOptions || {});
 
public/javascripts/lib/atjs/jquery.atwho.css (added)
+++ public/javascripts/lib/atjs/jquery.atwho.css
@@ -0,0 +1,46 @@
+.atwho-view {
+    position:absolute;
+    top: 0;
+    left: 0;
+    display: none;
+    margin-top: 18px;
+    background: white;
+    border: 1px solid #DDD;
+    border-radius: 3px;
+    box-shadow: 0 0 5px rgba(0,0,0,0.1);
+    min-width: 120px;
+    z-index: 1000;
+}
+
+.atwho-view .cur {
+    background: #3366FF;
+    color: white;
+}
+.atwho-view .cur small {
+    color: white;
+}
+.atwho-view strong {
+    color: #3366FF;
+}
+.atwho-view .cur strong {
+    color: white;
+    font:bold;
+}
+.atwho-view ul {
+    /* width: 100px; */
+    list-style:none;
+    padding:0;
+    margin:auto;
+}
+.atwho-view ul li {
+    display: block;
+    padding: 5px 10px;
+    border-bottom: 1px solid #DDD;
+    cursor: pointer;
+    /* border-top: 1px solid #C8C8C8; */
+}
+.atwho-view small {
+    font-size: smaller;
+    color: #777;
+    font-weight: normal;
+}
 
public/javascripts/lib/atjs/jquery.atwho.js (added)
+++ public/javascripts/lib/atjs/jquery.atwho.js
@@ -0,0 +1,809 @@
+/*! jquery.atwho - v0.4.10 - 2014-04-18
+* Copyright (c) 2014 chord.luo <chord.luo@gmail.com>; 
+* homepage: http://ichord.github.com/At.js 
+* Licensed MIT
+*/
+
+(function() {
+  (function(factory) {
+    if (typeof define === 'function' && define.amd) {
+      return define(['jquery'], factory);
+    } else {
+      return factory(window.jQuery);
+    }
+  })(function($) {
+
+var $CONTAINER, Api, App, Atwho, Controller, DEFAULT_CALLBACKS, KEY_CODE, Model, View,
+  __slice = [].slice;
+
+App = (function() {
+  function App(inputor) {
+    this.current_flag = null;
+    this.controllers = {};
+    this.alias_maps = {};
+    this.$inputor = $(inputor);
+    this.iframe = null;
+    this.setIframe();
+    this.listen();
+  }
+
+  App.prototype.setIframe = function(iframe) {
+    var error;
+    if (iframe) {
+      this.window = iframe.contentWindow;
+      this.document = iframe.contentDocument || this.window.document;
+      this.iframe = iframe;
+      return this;
+    } else {
+      this.document = this.$inputor[0].ownerDocument;
+      this.window = this.document.defaultView || this.document.parentWindow;
+      try {
+        return this.iframe = this.window.frameElement;
+      } catch (_error) {
+        error = _error;
+      }
+    }
+  };
+
+  App.prototype.controller = function(at) {
+    return this.controllers[this.alias_maps[at] || at || this.current_flag];
+  };
+
+  App.prototype.set_context_for = function(at) {
+    this.current_flag = at;
+    return this;
+  };
+
+  App.prototype.reg = function(flag, setting) {
+    var controller, _base;
+    controller = (_base = this.controllers)[flag] || (_base[flag] = new Controller(this, flag));
+    if (setting.alias) {
+      this.alias_maps[setting.alias] = flag;
+    }
+    controller.init(setting);
+    return this;
+  };
+
+  App.prototype.listen = function() {
+    return this.$inputor.on('keyup.atwhoInner', (function(_this) {
+      return function(e) {
+        return _this.on_keyup(e);
+      };
+    })(this)).on('keydown.atwhoInner', (function(_this) {
+      return function(e) {
+        return _this.on_keydown(e);
+      };
+    })(this)).on('scroll.atwhoInner', (function(_this) {
+      return function(e) {
+        var _ref;
+        return (_ref = _this.controller()) != null ? _ref.view.hide() : void 0;
+      };
+    })(this)).on('blur.atwhoInner', (function(_this) {
+      return function(e) {
+        var c;
+        if (c = _this.controller()) {
+          return c.view.hide(c.get_opt("display_timeout"));
+        }
+      };
+    })(this));
+  };
+
+  App.prototype.shutdown = function() {
+    var c, _, _ref;
+    _ref = this.controllers;
+    for (_ in _ref) {
+      c = _ref[_];
+      c.destroy();
+      delete this.controllers[_];
+    }
+    return this.$inputor.off('.atwhoInner');
+  };
+
+  App.prototype.dispatch = function() {
+    return $.map(this.controllers, (function(_this) {
+      return function(c) {
+        if (c.look_up()) {
+          return _this.set_context_for(c.at);
+        }
+      };
+    })(this));
+  };
+
+  App.prototype.on_keyup = function(e) {
+    var _ref;
+    switch (e.keyCode) {
+      case KEY_CODE.ESC:
+        e.preventDefault();
+        if ((_ref = this.controller()) != null) {
+          _ref.view.hide();
+        }
+        break;
+      case KEY_CODE.DOWN:
+      case KEY_CODE.UP:
+      case KEY_CODE.CTRL:
+        $.noop();
+        break;
+      case KEY_CODE.P:
+      case KEY_CODE.N:
+        if (!e.ctrlKey) {
+          this.dispatch();
+        }
+        break;
+      default:
+        this.dispatch();
+    }
+  };
+
+  App.prototype.on_keydown = function(e) {
+    var view, _ref;
+    view = (_ref = this.controller()) != null ? _ref.view : void 0;
+    if (!(view && view.visible())) {
+      return;
+    }
+    switch (e.keyCode) {
+      case KEY_CODE.ESC:
+        e.preventDefault();
+        view.hide();
+        break;
+      case KEY_CODE.UP:
+        e.preventDefault();
+        view.prev();
+        break;
+      case KEY_CODE.DOWN:
+        e.preventDefault();
+        view.next();
+        break;
+      case KEY_CODE.P:
+        if (!e.ctrlKey) {
+          return;
+        }
+        e.preventDefault();
+        view.prev();
+        break;
+      case KEY_CODE.N:
+        if (!e.ctrlKey) {
+          return;
+        }
+        e.preventDefault();
+        view.next();
+        break;
+      case KEY_CODE.TAB:
+      case KEY_CODE.ENTER:
+        if (!view.visible()) {
+          return;
+        }
+        e.preventDefault();
+        view.choose();
+        break;
+      default:
+        $.noop();
+    }
+  };
+
+  return App;
+
+})();
+
+Controller = (function() {
+  Controller.prototype.uid = function() {
+    return (Math.random().toString(16) + "000000000").substr(2, 8) + (new Date().getTime());
+  };
+
+  function Controller(app, at) {
+    this.app = app;
+    this.at = at;
+    this.$inputor = this.app.$inputor;
+    this.id = this.$inputor[0].id || this.uid();
+    this.setting = null;
+    this.query = null;
+    this.pos = 0;
+    this.cur_rect = null;
+    this.range = null;
+    $CONTAINER.append(this.$el = $("<div id='atwho-ground-" + this.id + "'></div>"));
+    this.model = new Model(this);
+    this.view = new View(this);
+  }
+
+  Controller.prototype.init = function(setting) {
+    this.setting = $.extend({}, this.setting || $.fn.atwho["default"], setting);
+    this.view.init();
+    return this.model.reload(this.setting.data);
+  };
+
+  Controller.prototype.destroy = function() {
+    this.trigger('beforeDestroy');
+    this.model.destroy();
+    this.view.destroy();
+    return this.$el.remove();
+  };
+
+  Controller.prototype.call_default = function() {
+    var args, error, func_name;
+    func_name = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
+    try {
+      return DEFAULT_CALLBACKS[func_name].apply(this, args);
+    } catch (_error) {
+      error = _error;
+      return $.error("" + error + " Or maybe At.js doesn't have function " + func_name);
+    }
+  };
+
+  Controller.prototype.trigger = function(name, data) {
+    var alias, event_name;
+    if (data == null) {
+      data = [];
+    }
+    data.push(this);
+    alias = this.get_opt('alias');
+    event_name = alias ? "" + name + "-" + alias + ".atwho" : "" + name + ".atwho";
+    return this.$inputor.trigger(event_name, data);
+  };
+
+  Controller.prototype.callbacks = function(func_name) {
+    return this.get_opt("callbacks")[func_name] || DEFAULT_CALLBACKS[func_name];
+  };
+
+  Controller.prototype.get_opt = function(at, default_value) {
+    var e;
+    try {
+      return this.setting[at];
+    } catch (_error) {
+      e = _error;
+      return null;
+    }
+  };
+
+  Controller.prototype.content = function() {
+    if (this.$inputor.is('textarea, input')) {
+      return this.$inputor.val();
+    } else {
+      return this.$inputor.text();
+    }
+  };
+
+  Controller.prototype.catch_query = function() {
+    var caret_pos, content, end, query, start, subtext;
+    content = this.content();
+    caret_pos = this.$inputor.caret('pos');
+    subtext = content.slice(0, caret_pos);
+    query = this.callbacks("matcher").call(this, this.at, subtext, this.get_opt('start_with_space'));
+    if (typeof query === "string" && query.length <= this.get_opt('max_len', 20)) {
+      start = caret_pos - query.length;
+      end = start + query.length;
+      this.pos = start;
+      query = {
+        'text': query,
+        'head_pos': start,
+        'end_pos': end
+      };
+      this.trigger("matched", [this.at, query.text]);
+    } else {
+      this.view.hide();
+    }
+    return this.query = query;
+  };
+
+  Controller.prototype.rect = function() {
+    var c, scale_bottom;
+    if (!(c = this.$inputor.caret({
+      iframe: this.app.iframe
+    }).caret('offset', this.pos - 1))) {
+      return;
+    }
+    if (this.$inputor.attr('contentEditable') === 'true') {
+      c = (this.cur_rect || (this.cur_rect = c)) || c;
+    }
+    scale_bottom = this.app.document.selection ? 0 : 2;
+    return {
+      left: c.left,
+      top: c.top,
+      bottom: c.top + c.height + scale_bottom
+    };
+  };
+
+  Controller.prototype.reset_rect = function() {
+    if (this.$inputor.attr('contentEditable') === 'true') {
+      return this.cur_rect = null;
+    }
+  };
+
+  Controller.prototype.mark_range = function() {
+    if (this.$inputor.attr('contentEditable') === 'true') {
+      if (this.app.window.getSelection) {
+        this.range = this.app.window.getSelection().getRangeAt(0);
+      }
+      if (this.app.document.selection) {
+        return this.ie8_range = this.app.document.selection.createRange();
+      }
+    }
+  };
+
+  Controller.prototype.insert_content_for = function($li) {
+    var data, data_value, tpl;
+    data_value = $li.data('value');
+    tpl = this.get_opt('insert_tpl');
+    if (this.$inputor.is('textarea, input') || !tpl) {
+      return data_value;
+    }
+    data = $.extend({}, $li.data('item-data'), {
+      'atwho-data-value': data_value,
+      'atwho-at': this.at
+    });
+    return this.callbacks("tpl_eval").call(this, tpl, data);
+  };
+
+  Controller.prototype.insert = function(content, $li) {
+    var $inputor, $insert_node, class_name, content_node, insert_node, pos, range, sel, source, start_str, text;
+    $inputor = this.$inputor;
+    if ($inputor.attr('contentEditable') === 'true') {
+      class_name = "atwho-view-flag atwho-view-flag-" + (this.get_opt('alias') || this.at);
+      content_node = "" + content + "<span contenteditable='false'>&nbsp;<span>";
+      insert_node = "<span contenteditable='false' class='" + class_name + "'>" + content_node + "</span>";
+      $insert_node = $(insert_node, this.app.document).data('atwho-data-item', $li.data('item-data'));
+      if (this.app.document.selection) {
+        $insert_node = $("<span contenteditable='true'></span>", this.app.document).html($insert_node);
+      }
+    }
+    if ($inputor.is('textarea, input')) {
+      content = '' + content;
+      source = $inputor.val();
+      start_str = source.slice(0, Math.max(this.query.head_pos - this.at.length, 0));
+      text = "" + start_str + content + " " + (source.slice(this.query['end_pos'] || 0));
+      $inputor.val(text);
+      $inputor.caret('pos', start_str.length + content.length + 1);
+    } else if (range = this.range) {
+      pos = range.startOffset - (this.query.end_pos - this.query.head_pos) - this.at.length;
+      range.setStart(range.endContainer, Math.max(pos, 0));
+      range.setEnd(range.endContainer, range.endOffset);
+      range.deleteContents();
+      range.insertNode($insert_node[0]);
+      range.collapse(false);
+      sel = this.app.window.getSelection();
+      sel.removeAllRanges();
+      sel.addRange(range);
+    } else if (range = this.ie8_range) {
+      range.moveStart('character', this.query.end_pos - this.query.head_pos - this.at.length);
+      range.pasteHTML(content_node);
+      range.collapse(false);
+      range.select();
+    }
+    if (!$inputor.is(':focus')) {
+      $inputor.focus();
+    }
+    return $inputor.change();
+  };
+
+  Controller.prototype.render_view = function(data) {
+    var search_key;
+    search_key = this.get_opt("search_key");
+    data = this.callbacks("sorter").call(this, this.query.text, data.slice(0, 1001), search_key);
+    return this.view.render(data.slice(0, this.get_opt('limit')));
+  };
+
+  Controller.prototype.look_up = function() {
+    var query, _callback;
+    if (!(query = this.catch_query())) {
+      return;
+    }
+    _callback = function(data) {
+      if (data && data.length > 0) {
+        return this.render_view(data);
+      } else {
+        return this.view.hide();
+      }
+    };
+    this.model.query(query.text, $.proxy(_callback, this));
+    return query;
+  };
+
+  return Controller;
+
+})();
+
+Model = (function() {
+  function Model(context) {
+    this.context = context;
+    this.at = this.context.at;
+    this.storage = this.context.$inputor;
+  }
+
+  Model.prototype.destroy = function() {
+    return this.storage.data(this.at, null);
+  };
+
+  Model.prototype.saved = function() {
+    return this.fetch() > 0;
+  };
+
+  Model.prototype.query = function(query, callback) {
+    var data, search_key, _remote_filter;
+    data = this.fetch();
+    search_key = this.context.get_opt("search_key");
+    data = this.context.callbacks('filter').call(this.context, query, data, search_key) || [];
+    _remote_filter = this.context.callbacks('remote_filter');
+    if (data.length > 0 || (!_remote_filter && data.length === 0)) {
+      return callback(data);
+    } else {
+      return _remote_filter.call(this.context, query, callback);
+    }
+  };
+
+  Model.prototype.fetch = function() {
+    return this.storage.data(this.at) || [];
+  };
+
+  Model.prototype.save = function(data) {
+    return this.storage.data(this.at, this.context.callbacks("before_save").call(this.context, data || []));
+  };
+
+  Model.prototype.load = function(data) {
+    if (!(this.saved() || !data)) {
+      return this._load(data);
+    }
+  };
+
+  Model.prototype.reload = function(data) {
+    return this._load(data);
+  };
+
+  Model.prototype._load = function(data) {
+    if (typeof data === "string") {
+      return $.ajax(data, {
+        dataType: "json"
+      }).done((function(_this) {
+        return function(data) {
+          return _this.save(data);
+        };
+      })(this));
+    } else {
+      return this.save(data);
+    }
+  };
+
+  return Model;
+
+})();
+
+View = (function() {
+  function View(context) {
+    this.context = context;
+    this.$el = $("<div class='atwho-view'><ul class='atwho-view-ul'></ul></div>");
+    this.timeout_id = null;
+    this.context.$el.append(this.$el);
+    this.bind_event();
+  }
+
+  View.prototype.init = function() {
+    var id;
+    id = this.context.get_opt("alias") || this.context.at.charCodeAt(0);
+    return this.$el.attr({
+      'id': "at-view-" + id
+    });
+  };
+
+  View.prototype.destroy = function() {
+    return this.$el.remove();
+  };
+
+  View.prototype.bind_event = function() {
+    var $menu;
+    $menu = this.$el.find('ul');
+    return $menu.on('mouseenter.atwho-view', 'li', function(e) {
+      $menu.find('.cur').removeClass('cur');
+      return $(e.currentTarget).addClass('cur');
+    }).on('click', (function(_this) {
+      return function(e) {
+          /*$("#body").focus();
+         var e = jQuery.Event("keydown", {keyCode:65});
+         $("#body").trigger(e);*/
+        _this.choose();
+        return e.preventDefault();
+      };
+    })(this));
+  };
+
+  View.prototype.visible = function() {
+    return this.$el.is(":visible");
+  };
+
+  View.prototype.choose = function() {
+    var $li, content;
+    if (($li = this.$el.find(".cur")).length) {
+        content = this.context.insert_content_for($li);
+      this.context.insert(this.context.callbacks("before_insert").call(this.context, content, $li), $li);
+      this.context.trigger("inserted", [$li]);
+      return this.hide();
+    }
+  };
+
+  View.prototype.reposition = function(rect) {
+    var offset, _ref;
+    if (rect.bottom + this.$el.height() - $(window).scrollTop() > $(window).height()) {
+      rect.bottom = rect.top - this.$el.height();
+    }
+    offset = {
+      left: rect.left,
+      top: rect.bottom
+    };
+    if ((_ref = this.context.callbacks("before_reposition")) != null) {
+      _ref.call(this.context, offset);
+    }
+    this.$el.offset(offset);
+    return this.context.trigger("reposition", [offset]);
+  };
+
+  View.prototype.next = function() {
+    var cur, next;
+    cur = this.$el.find('.cur').removeClass('cur');
+    next = cur.next();
+    if (!next.length) {
+      next = this.$el.find('li:first');
+    }
+    return next.addClass('cur');
+  };
+
+  View.prototype.prev = function() {
+    var cur, prev;
+    cur = this.$el.find('.cur').removeClass('cur');
+    prev = cur.prev();
+    if (!prev.length) {
+      prev = this.$el.find('li:last');
+    }
+    return prev.addClass('cur');
+  };
+
+  View.prototype.show = function() {
+    var rect;
+    this.context.mark_range();
+    if (!this.visible()) {
+      this.$el.show();
+      this.context.trigger('shown');
+    }
+    if (rect = this.context.rect()) {
+      return this.reposition(rect);
+    }
+  };
+
+  View.prototype.hide = function(time) {
+    var callback;
+    if (isNaN(time && this.visible())) {
+      this.context.reset_rect();
+      this.$el.hide();
+      return this.context.trigger('hidden');
+    } else {
+      callback = (function(_this) {
+        return function() {
+          return _this.hide();
+        };
+      })(this);
+      clearTimeout(this.timeout_id);
+      return this.timeout_id = setTimeout(callback, time);
+    }
+  };
+
+  View.prototype.render = function(list) {
+    var $li, $ul, item, li, tpl, _i, _len;
+    if (!($.isArray(list) && list.length > 0)) {
+      this.hide();
+      return;
+    }
+    this.$el.find('ul').empty();
+    $ul = this.$el.find('ul');
+    tpl = this.context.get_opt('tpl');
+    for (_i = 0, _len = list.length; _i < _len; _i++) {
+      item = list[_i];
+      item = $.extend({}, item, {
+        'atwho-at': this.context.at
+      });
+      li = this.context.callbacks("tpl_eval").call(this.context, tpl, item);
+      $li = $(this.context.callbacks("highlighter").call(this.context, li, this.context.query.text));
+      $li.data("item-data", item);
+      $ul.append($li);
+    }
+    this.show();
+    if (this.context.get_opt('highlight_first')) {
+      return $ul.find("li:first").addClass("cur");
+    }
+  };
+
+  return View;
+
+})();
+
+KEY_CODE = {
+  DOWN: 40,
+  UP: 38,
+  ESC: 27,
+  TAB: 9,
+  ENTER: 13,
+  CTRL: 17,
+  P: 80,
+  N: 78
+};
+
+DEFAULT_CALLBACKS = {
+  before_save: function(data) {
+    var item, _i, _len, _results;
+    if (!$.isArray(data)) {
+      return data;
+    }
+    _results = [];
+    for (_i = 0, _len = data.length; _i < _len; _i++) {
+      item = data[_i];
+      if ($.isPlainObject(item)) {
+        _results.push(item);
+      } else {
+        _results.push({
+          name: item
+        });
+      }
+    }
+    return _results;
+  },
+  matcher: function(flag, subtext, should_start_with_space) {
+    var match, regexp;
+    flag = flag.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
+    if (should_start_with_space) {
+      flag = '(?:^|\\s)' + flag;
+    }
+    regexp = new RegExp(flag + '([A-Za-z0-9_\+\-]*)$|' + flag + '([^\\x00-\\xff]*)$', 'gi');
+    match = regexp.exec(subtext);
+    if (match) {
+      return match[2] || match[1];
+    } else {
+      return null;
+    }
+  },
+  filter: function(query, data, search_key) {
+    var item, _i, _len, _results;
+    _results = [];
+    for (_i = 0, _len = data.length; _i < _len; _i++) {
+      item = data[_i];
+      if (~item[search_key].toLowerCase().indexOf(query.toLowerCase())) {
+        _results.push(item);
+      }
+    }
+    return _results;
+  },
+  remote_filter: null,
+  sorter: function(query, items, search_key) {
+    var item, _i, _len, _results;
+    if (!query) {
+      return items;
+    }
+    _results = [];
+    for (_i = 0, _len = items.length; _i < _len; _i++) {
+      item = items[_i];
+      item.atwho_order = item[search_key].toLowerCase().indexOf(query.toLowerCase());
+      if (item.atwho_order > -1) {
+        _results.push(item);
+      }
+    }
+    return _results.sort(function(a, b) {
+      return a.atwho_order - b.atwho_order;
+    });
+  },
+  tpl_eval: function(tpl, map) {
+    var error;
+    try {
+      return tpl.replace(/\$\{([^\}]*)\}/g, function(tag, key, pos) {
+        return map[key];
+      });
+    } catch (_error) {
+      error = _error;
+      return "";
+    }
+  },
+  highlighter: function(li, query) {
+    var regexp;
+    if (!query) {
+      return li;
+    }
+    regexp = new RegExp(">\\s*(\\w*)(" + query.replace("+", "\\+") + ")(\\w*)\\s*<", 'ig');
+    return li.replace(regexp, function(str, $1, $2, $3) {
+      return '> ' + $1 + '<strong>' + $2 + '</strong>' + $3 + ' <';
+    });
+  },
+  before_insert: function(value, $li) {
+    return value;
+  }
+};
+
+Api = {
+  load: function(at, data) {
+    var c;
+    if (c = this.controller(at)) {
+      return c.model.load(data);
+    }
+  },
+  getInsertedItemsWithIDs: function(at) {
+    var c, ids, items;
+    if (!(c = this.controller(at))) {
+      return [null, null];
+    }
+    if (at) {
+      at = "-" + (c.get_opt('alias') || c.at);
+    }
+    ids = [];
+    items = $.map(this.$inputor.find("span.atwho-view-flag" + (at || "")), function(item) {
+      var data;
+      data = $(item).data('atwho-data-item');
+      if (ids.indexOf(data.id) > -1) {
+        return;
+      }
+      if (data.id) {
+        ids.push = data.id;
+      }
+      return data;
+    });
+    return [ids, items];
+  },
+  getInsertedItems: function(at) {
+    return Api.getInsertedItemsWithIDs.apply(this, [at])[1];
+  },
+  getInsertedIDs: function(at) {
+    return Api.getInsertedItemsWithIDs.apply(this, [at])[0];
+  },
+  setIframe: function(iframe) {
+    return this.setIframe(iframe);
+  },
+  run: function() {
+    return this.dispatch();
+  },
+  destroy: function() {
+    this.shutdown();
+    return this.$inputor.data('atwho', null);
+  }
+};
+
+Atwho = {
+  init: function(options) {
+    var $this, app;
+    app = ($this = $(this)).data("atwho");
+    if (!app) {
+      $this.data('atwho', (app = new App(this)));
+    }
+    app.reg(options.at, options);
+    return this;
+  }
+};
+
+$CONTAINER = $("<div id='atwho-container'></div>");
+
+$.fn.atwho = function(method) {
+  var result, _args;
+  _args = arguments;
+  $('body').append($CONTAINER);
+  result = null;
+  this.filter('textarea, input, [contenteditable=true]').each(function() {
+    var app;
+    if (typeof method === 'object' || !method) {
+      return Atwho.init.apply(this, _args);
+    } else if (Api[method]) {
+      if (app = $(this).data('atwho')) {
+        return result = Api[method].apply(app, Array.prototype.slice.call(_args, 1));
+      }
+    } else {
+      return $.error("Method " + method + " does not exist on jQuery.caret");
+    }
+  });
+  return result || this;
+};
+
+$.fn.atwho["default"] = {
+  at: void 0,
+  alias: void 0,
+  data: null,
+  tpl: "<li data-value='${atwho-at}${name}'>${name}</li>",
+  insert_tpl: "<span>${atwho-data-value}</span>",
+  callbacks: DEFAULT_CALLBACKS,
+  search_key: "name",
+  start_with_space: true,
+  highlight_first: true,
+  limit: 5,
+  max_len: 20,
+  display_timeout: 300
+};
+
+  });
+}).call(this);
 
public/javascripts/lib/atjs/jquery.browser.js (added)
+++ public/javascripts/lib/atjs/jquery.browser.js
@@ -0,0 +1,112 @@
+/*!
+ * jQuery Browser Plugin v0.0.6
+ * https://github.com/gabceb/jquery-browser-plugin
+ *
+ * Original jquery-browser code Copyright 2005, 2013 jQuery Foundation, Inc. and other contributors
+ * http://jquery.org/license
+ *
+ * Modifications Copyright 2013 Gabriel Cebrian
+ * https://github.com/gabceb
+ *
+ * Released under the MIT license
+ *
+ * Date: 2013-07-29T17:23:27-07:00
+ */
+
+(function( jQuery, window, undefined ) {
+  "use strict";
+
+  var matched, browser;
+
+  jQuery.uaMatch = function( ua ) {
+    ua = ua.toLowerCase();
+
+  	var match = /(opr)[\/]([\w.]+)/.exec( ua ) ||
+  		/(chrome)[ \/]([\w.]+)/.exec( ua ) ||
+  		/(version)[ \/]([\w.]+).*(safari)[ \/]([\w.]+)/.exec( ua ) ||
+  		/(webkit)[ \/]([\w.]+)/.exec( ua ) ||
+  		/(opera)(?:.*version|)[ \/]([\w.]+)/.exec( ua ) ||
+  		/(msie) ([\w.]+)/.exec( ua ) ||
+  		ua.indexOf("trident") >= 0 && /(rv)(?::| )([\w.]+)/.exec( ua ) ||
+  		ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec( ua ) ||
+  		[];
+
+  	var platform_match = /(ipad)/.exec( ua ) ||
+  		/(iphone)/.exec( ua ) ||
+  		/(android)/.exec( ua ) ||
+  		/(windows phone)/.exec( ua ) ||
+  		/(win)/.exec( ua ) ||
+  		/(mac)/.exec( ua ) ||
+  		/(linux)/.exec( ua ) ||
+  		/(cros)/i.exec( ua ) ||
+  		[];
+
+  	return {
+  		browser: match[ 3 ] || match[ 1 ] || "",
+  		version: match[ 2 ] || "0",
+  		platform: platform_match[ 0 ] || ""
+  	};
+  };
+
+  matched = jQuery.uaMatch( window.navigator.userAgent );
+  browser = {};
+
+  if ( matched.browser ) {
+  	browser[ matched.browser ] = true;
+  	browser.version = matched.version;
+  	browser.versionNumber = parseInt(matched.version);
+  }
+
+  if ( matched.platform ) {
+  	browser[ matched.platform ] = true;
+  }
+
+  // These are all considered mobile platforms, meaning they run a mobile browser
+  if ( browser.android || browser.ipad || browser.iphone || browser[ "windows phone" ] ) {
+  	browser.mobile = true;
+  }
+
+  // These are all considered desktop platforms, meaning they run a desktop browser
+  if ( browser.cros || browser.mac || browser.linux || browser.win ) {
+  	browser.desktop = true;
+  }
+
+  // Chrome, Opera 15+ and Safari are webkit based browsers
+  if ( browser.chrome || browser.opr || browser.safari ) {
+  	browser.webkit = true;
+  }
+
+  // IE11 has a new token so we will assign it msie to avoid breaking changes
+  if ( browser.rv )
+  {
+  	var ie = "msie";
+
+  	matched.browser = ie;
+  	browser[ie] = true;
+  }
+
+  // Opera 15+ are identified as opr
+  if ( browser.opr )
+  {
+  	var opera = "opera";
+
+  	matched.browser = opera;
+  	browser[opera] = true;
+  }
+
+  // Stock Android browsers are marked as Safari on Android.
+  if ( browser.safari && browser.android )
+  {
+  	var android = "android";
+
+  	matched.browser = android;
+  	browser[android] = true;
+  }
+
+  // Assign the name and platform variable
+  browser.name = matched.browser;
+  browser.platform = matched.platform;
+
+
+  jQuery.browser = browser;
+})( jQuery, window );
 
public/javascripts/lib/atjs/jquery.caret.min.js (added)
+++ public/javascripts/lib/atjs/jquery.caret.min.js
@@ -0,0 +1,2 @@
+/*! jquery.caret 2014-04-18 */
+(function(){!function(a){return"function"==typeof define&&define.amd?define(["jquery"],a):a(window.jQuery)}(function(a){"use strict";var b,c,d,e,f,g,h,i,j,k,l;return k="caret",b=function(){function b(a){this.$inputor=a,this.domInputor=this.$inputor[0]}return b.prototype.setPos=function(){return this.domInputor},b.prototype.getIEPosition=function(){return a.noop()},b.prototype.getPosition=function(){return a.noop()},b.prototype.getOldIEPos=function(){var a,b;return b=h.selection.createRange(),a=h.body.createTextRange(),a.moveToElementText(this.domInputor),a.setEndPoint("EndToEnd",b),a.text.length},b.prototype.getPos=function(){var a,b,c;return(c=this.range())?(a=c.cloneRange(),a.selectNodeContents(this.domInputor),a.setEnd(c.endContainer,c.endOffset),b=a.toString().length,a.detach(),b):h.selection?this.getOldIEPos():void 0},b.prototype.getOldIEOffset=function(){var a,b;return a=h.selection.createRange().duplicate(),a.moveStart("character",-1),b=a.getBoundingClientRect(),{height:b.bottom-b.top,left:b.left,top:b.top}},b.prototype.getOffset=function(){var b,c,d,e;if(j.getSelection&&(d=this.range())){if(d.endOffset-1<0)return null;b=d.cloneRange(),b.setStart(d.endContainer,d.endOffset-1),b.setEnd(d.endContainer,d.endOffset),e=b.getBoundingClientRect(),c={height:e.height,left:e.left+e.width,top:e.top},b.detach()}else h.selection&&(c=this.getOldIEOffset());return c&&!i&&(c.top+=a(j).scrollTop(),c.left+=a(j).scrollLeft()),c},b.prototype.range=function(){var a;if(j.getSelection)return a=j.getSelection(),a.rangeCount>0?a.getRangeAt(0):null},b}(),c=function(){function b(a){this.$inputor=a,this.domInputor=this.$inputor[0]}return b.prototype.getIEPos=function(){var a,b,c,d,e,f,g;return b=this.domInputor,f=h.selection.createRange(),e=0,f&&f.parentElement()===b&&(d=b.value.replace(/\r\n/g,"\n"),c=d.length,g=b.createTextRange(),g.moveToBookmark(f.getBookmark()),a=b.createTextRange(),a.collapse(!1),e=g.compareEndPoints("StartToEnd",a)>-1?c:-g.moveStart("character",-c)),e},b.prototype.getPos=function(){return h.selection?this.getIEPos():this.domInputor.selectionStart},b.prototype.setPos=function(a){var b,c;return b=this.domInputor,h.selection?(c=b.createTextRange(),c.move("character",a),c.select()):b.setSelectionRange&&b.setSelectionRange(a,a),b},b.prototype.getIEOffset=function(a){var b,c,d,e;return c=this.domInputor.createTextRange(),a||(a=this.getPos()),c.move("character",a),d=c.boundingLeft,e=c.boundingTop,b=c.boundingHeight,{left:d,top:e,height:b}},b.prototype.getOffset=function(b){var c,d,e;return c=this.$inputor,h.selection?(d=this.getIEOffset(b),d.top+=a(j).scrollTop()+c.scrollTop(),d.left+=a(j).scrollLeft()+c.scrollLeft(),d):(d=c.offset(),e=this.getPosition(b),d={left:d.left+e.left-c.scrollLeft(),top:d.top+e.top-c.scrollTop(),height:e.height})},b.prototype.getPosition=function(a){var b,c,e,f,g,h;return b=this.$inputor,e=function(a){return a.replace(/</g,"&lt").replace(/>/g,"&gt").replace(/`/g,"&#96").replace(/"/g,"&quot").replace(/\r\n|\r|\n/g,"<br />")},void 0===a&&(a=this.getPos()),h=b.val().slice(0,a),f="<span>"+e(h)+"</span>",f+="<span id='caret'>|</span>",g=new d(b),c=g.create(f).rect()},b.prototype.getIEPosition=function(a){var b,c,d,e,f;return d=this.getIEOffset(a),c=this.$inputor.offset(),e=d.left-c.left,f=d.top-c.top,b=d.height,{left:e,top:f,height:b}},b}(),d=function(){function b(a){this.$inputor=a}return b.prototype.css_attr=["overflowY","height","width","paddingTop","paddingLeft","paddingRight","paddingBottom","marginTop","marginLeft","marginRight","marginBottom","fontFamily","borderStyle","borderWidth","wordWrap","fontSize","lineHeight","overflowX","text-align"],b.prototype.mirrorCss=function(){var b,c=this;return b={position:"absolute",left:-9999,top:0,zIndex:-2e4,"white-space":"pre-wrap"},a.each(this.css_attr,function(a,d){return b[d]=c.$inputor.css(d)}),b},b.prototype.create=function(b){return this.$mirror=a("<div></div>"),this.$mirror.css(this.mirrorCss()),this.$mirror.html(b),this.$inputor.after(this.$mirror),this},b.prototype.rect=function(){var a,b,c;return a=this.$mirror.find("#caret"),b=a.position(),c={left:b.left,top:b.top,height:a.height()},this.$mirror.remove(),c},b}(),e={contentEditable:function(a){return!(!a[0].contentEditable||"true"!==a[0].contentEditable)}},g={pos:function(a){return a||0===a?this.setPos(a):this.getPos()},position:function(a){return h.selection?this.getIEPosition(a):this.getPosition(a)},offset:function(b){var c,d;return d=this.getOffset(b),i&&(c=a(i).offset(),d.top+=c.top,d.left+=c.left),d}},h=null,j=null,i=null,l=function(a){return i=a,j=a.contentWindow,h=a.contentDocument||j.document},f=function(b,c){var d,e;if(a.isPlainObject(c)&&(e=c.iframe))return b.data("caret-iframe",e),l(e);if(e=b.data("caret-iframe"))return l(e);h=b[0].ownerDocument,j=h.defaultView||h.parentWindow;try{return i=j.frameElement}catch(f){d=f}},a.fn.caret=function(d){var h;return"object"==typeof d?(f(this,d),this):g[d]?(f(this),h=e.contentEditable(this)?new b(this):new c(this),g[d].apply(h,Array.prototype.slice.call(arguments,1))):a.error("Method "+d+" does not exist on jQuery.caret")},a.fn.caret.EditableCaret=b,a.fn.caret.InputCaret=c,a.fn.caret.Utils=e,a.fn.caret.apis=g})}).call(this);(No newline at end of file)
Add a comment
List