[Notice] Announcing the End of Demo Server [Read me]

Merge branch 'master' of https://github.com/nforge/hive
@e5e1aab368beed3ca920c2ef10be5f53d39991ac
--- app/assets/stylesheets/less/_page.less
+++ app/assets/stylesheets/less/_page.less
... | ... | @@ -1241,6 +1241,7 @@ |
1241 | 1241 |
float: left; |
1242 | 1242 |
.header { |
1243 | 1243 |
font-size: 20px; |
1244 |
+ font-weight: bold; |
|
1244 | 1245 |
margin-bottom: 15px; |
1245 | 1246 |
.project-name { |
1246 | 1247 |
color: #888; |
... | ... | @@ -2110,8 +2111,6 @@ |
2110 | 2111 |
color: #222; |
2111 | 2112 |
font-size: 14px; |
2112 | 2113 |
line-height: 40px; |
2113 |
- border-bottom: 1px solid @white; |
|
2114 |
- .box-shadow(inset 0 -1px 0 rgba(0, 0, 0, 0.15)); |
|
2115 | 2114 |
} |
2116 | 2115 |
.comments { |
2117 | 2116 |
margin: 0; |
... | ... | @@ -2119,8 +2118,6 @@ |
2119 | 2118 |
list-style: none; |
2120 | 2119 |
.comment { |
2121 | 2120 |
padding: 20px; |
2122 |
- border-bottom: 1px solid @white; |
|
2123 |
- .box-shadow(inset 0 -1px 0 rgba(0, 0, 0, 0.15)); |
|
2124 | 2121 |
.media-body { |
2125 | 2122 |
padding-left: 17px; |
2126 | 2123 |
.commenter { |
... | ... | @@ -2220,7 +2217,6 @@ |
2220 | 2217 |
margin:20px 0; |
2221 | 2218 |
|
2222 | 2219 |
.labels-wrap { |
2223 |
- width: 650px; |
|
2224 | 2220 |
line-height: 30px; |
2225 | 2221 |
padding-right: 30px; |
2226 | 2222 |
text-align: left; |
... | ... | @@ -3277,4 +3273,4 @@ |
3277 | 3273 |
/* |
3278 | 3274 |
.alert { position:absolute; width:830px; top:0px; margin-left:auto; z-index:999; display:none; } |
3279 | 3275 |
form { margin:0 0 2px; } |
3280 |
-*/(파일 끝에 줄바꿈 문자 없음) |
|
3276 |
+*/ |
--- app/controllers/BoardApp.java
+++ app/controllers/BoardApp.java
... | ... | @@ -138,15 +138,17 @@ |
138 | 138 |
public static Result editPost(String userName, String projectName, Long postId) { |
139 | 139 |
Form<Posting> postForm = new Form<Posting>(Posting.class).bindFromRequest(); |
140 | 140 |
Project project = ProjectApp.getProject(userName, projectName); |
141 |
- Posting post = postForm.get(); |
|
142 |
- Posting original = Posting.finder.byId(postId); |
|
141 |
+ final Posting post = postForm.get(); |
|
142 |
+ final Posting original = Posting.finder.byId(postId); |
|
143 | 143 |
Call redirectTo = routes.BoardApp.posts(project.owner, project.name, 1); |
144 |
- Callback doNothing = new Callback() { |
|
144 |
+ Callback updatePostingBeforeUpdate = new Callback() { |
|
145 | 145 |
@Override |
146 |
- public void run() { } |
|
146 |
+ public void run() { |
|
147 |
+ post.comments = original.comments; |
|
148 |
+ } |
|
147 | 149 |
}; |
148 | 150 |
|
149 |
- return editPosting(original, post, postForm, redirectTo, doNothing); |
|
151 |
+ return editPosting(original, post, postForm, redirectTo, updatePostingBeforeUpdate); |
|
150 | 152 |
} |
151 | 153 |
|
152 | 154 |
public static Result deletePost(String userName, String projectName, Long postingId) { |
--- app/controllers/IssueApp.java
+++ app/controllers/IssueApp.java
... | ... | @@ -25,16 +25,13 @@ |
25 | 25 |
import com.avaje.ebean.Page; |
26 | 26 |
import com.avaje.ebean.ExpressionList; |
27 | 27 |
|
28 |
-import java.io.File; |
|
29 | 28 |
import java.io.IOException; |
30 | 29 |
import java.io.UnsupportedEncodingException; |
31 | 30 |
import java.util.ArrayList; |
32 | 31 |
import java.util.List; |
33 | 32 |
import java.util.Map; |
34 | 33 |
import java.util.Set; |
35 |
-import java.util.Map; |
|
36 | 34 |
|
37 |
-import static com.avaje.ebean.Expr.contains; |
|
38 | 35 |
import static com.avaje.ebean.Expr.icontains; |
39 | 36 |
|
40 | 37 |
public class IssueApp extends AbstractPostingApp { |
... | ... | @@ -149,14 +146,14 @@ |
149 | 146 |
|
150 | 147 |
public static Result issuesAsExcel(ExpressionList<Issue> el, Project project) |
151 | 148 |
throws WriteException, IOException, UnsupportedEncodingException { |
152 |
- File excelFile = Issue.excelSave(el.findList(), project.name + "_issues"); |
|
149 |
+ byte[] excelData = Issue.excelFrom(el.findList()); |
|
150 |
+ String filename = HttpUtil.encodeContentDisposition( |
|
151 |
+ project.name + "_issues_" + JodaDateUtil.today().getTime() + ".xls"); |
|
153 | 152 |
|
154 |
- String filename = HttpUtil.encodeContentDisposition(excelFile.getName()); |
|
155 |
- |
|
156 |
- response().setHeader("Content-Type", new Tika().detect(excelFile)); |
|
153 |
+ response().setHeader("Content-Type", new Tika().detect(filename)); |
|
157 | 154 |
response().setHeader("Content-Disposition", "attachment; " + filename); |
158 | 155 |
|
159 |
- return ok(excelFile); |
|
156 |
+ return ok(excelData); |
|
160 | 157 |
} |
161 | 158 |
|
162 | 159 |
public static Result issue(String userName, String projectName, Long issueId) { |
... | ... | @@ -246,7 +243,7 @@ |
246 | 243 |
Callback updateIssueBeforeSave = new Callback() { |
247 | 244 |
@Override |
248 | 245 |
public void run() { |
249 |
- issue.project = project; |
|
246 |
+ issue.comments = originalIssue.comments; |
|
250 | 247 |
addLabels(issue.labels, request()); |
251 | 248 |
} |
252 | 249 |
}; |
--- app/controllers/ProjectApp.java
+++ app/controllers/ProjectApp.java
... | ... | @@ -1,6 +1,7 @@ |
1 | 1 |
package controllers; |
2 | 2 |
|
3 | 3 |
import com.avaje.ebean.Page; |
4 |
+import com.avaje.ebean.ExpressionList; |
|
4 | 5 |
import models.*; |
5 | 6 |
import models.enumeration.Operation; |
6 | 7 |
import models.enumeration.RoleType; |
... | ... | @@ -32,12 +33,15 @@ |
32 | 33 |
import java.util.HashMap; |
33 | 34 |
import java.util.List; |
34 | 35 |
import java.util.Map; |
36 |
+import java.util.ArrayList; |
|
35 | 37 |
|
36 | 38 |
import static play.data.Form.form; |
37 | 39 |
import static play.libs.Json.toJson; |
40 |
+import static com.avaje.ebean.Expr.contains; |
|
38 | 41 |
|
39 | 42 |
public class ProjectApp extends Controller { |
40 |
- public static final String[] LOGO_TYPE = {"jpg", "png", "gif", "bmp"}; |
|
43 |
+ public static final String[] LOGO_TYPE = {"jpg", "jpeg", "png", "gif", "bmp"}; |
|
44 |
+ private static final int MAX_FETCH_PROJECTS = 1000; |
|
41 | 45 |
|
42 | 46 |
public static Project getProject(String userName, String projectName) { |
43 | 47 |
return Project.findByNameAndOwner(userName, projectName); |
... | ... | @@ -247,6 +251,30 @@ |
247 | 251 |
} |
248 | 252 |
|
249 | 253 |
public static Result projects(String filter, String state, int pageNum) { |
254 |
+ final String HTML = "text/html"; |
|
255 |
+ final String JSON = "application/json"; |
|
256 |
+ String prefer = HttpUtil.getPreferType(request(), JSON, HTML); |
|
257 |
+ |
|
258 |
+ if (prefer == null) { |
|
259 |
+ return status(Http.Status.NOT_ACCEPTABLE); |
|
260 |
+ } |
|
261 |
+ |
|
262 |
+ if (prefer.equals(JSON)) { |
|
263 |
+ String query = request().getQueryString("query"); |
|
264 |
+ List<String> projectNames = new ArrayList<String>(); |
|
265 |
+ ExpressionList<Project> el = Project.find.where().or(contains("name", query), contains("owner", query)); |
|
266 |
+ int total = el.findRowCount(); |
|
267 |
+ if (total > MAX_FETCH_PROJECTS) { |
|
268 |
+ el.setMaxRows(MAX_FETCH_PROJECTS); |
|
269 |
+ response().setHeader("Content-Range", "items " + MAX_FETCH_PROJECTS + "/" + total); |
|
270 |
+ } |
|
271 |
+ for (Project project: el.findList()) { |
|
272 |
+ projectNames.add(project.owner + "/" + project.name); |
|
273 |
+ } |
|
274 |
+ |
|
275 |
+ return ok(toJson(projectNames)); |
|
276 |
+ } |
|
277 |
+ |
|
250 | 278 |
OrderParams orderParams = new OrderParams(); |
251 | 279 |
SearchParams searchParams = new SearchParams(); |
252 | 280 |
|
--- app/controllers/SiteApp.java
+++ app/controllers/SiteApp.java
... | ... | @@ -1,28 +1,23 @@ |
1 | 1 |
package controllers; |
2 | 2 |
|
3 |
-import java.util.ArrayList; |
|
4 |
-import java.util.List; |
|
5 |
-import java.util.Map; |
|
6 |
- |
|
7 |
-import org.apache.commons.mail.*; |
|
8 |
- |
|
9 |
-import models.Project; |
|
10 |
-import models.User; |
|
3 |
+import com.avaje.ebean.Page; |
|
4 |
+import info.schleichardt.play2.mailplugin.Mailer; |
|
5 |
+import models.*; |
|
6 |
+import org.apache.commons.mail.EmailException; |
|
11 | 7 |
import org.apache.commons.mail.SimpleEmail; |
12 | 8 |
import play.Configuration; |
13 | 9 |
import play.Logger; |
10 |
+import play.i18n.Messages; |
|
14 | 11 |
import play.mvc.Controller; |
12 |
+import play.mvc.Http; |
|
15 | 13 |
import play.mvc.Result; |
16 | 14 |
import utils.Constants; |
15 |
+import views.html.site.*; |
|
17 | 16 |
|
18 |
-import views.html.site.setting; |
|
19 |
-import views.html.site.mail; |
|
20 |
-import views.html.site.userList; |
|
21 |
-import views.html.site.projectList; |
|
17 |
+import java.util.*; |
|
22 | 18 |
|
23 |
-import com.avaje.ebean.Page; |
|
24 | 19 |
import static play.data.Form.form; |
25 |
-import info.schleichardt.play2.mailplugin.Mailer; |
|
20 |
+import static play.libs.Json.toJson; |
|
26 | 21 |
|
27 | 22 |
public class SiteApp extends Controller { |
28 | 23 |
public static Result sendMail() throws EmailException{ |
... | ... | @@ -59,19 +54,44 @@ |
59 | 54 |
return ok(mail.render("title.sendMail", notConfiguredItems, sender, errorMessage, sended)); |
60 | 55 |
} |
61 | 56 |
|
57 |
+ public static Result massMail() { |
|
58 |
+ Configuration config = play.Play.application().configuration(); |
|
59 |
+ List<String> notConfiguredItems = new ArrayList<String>(); |
|
60 |
+ String[] requiredItems = {"smtp.host", "smtp.user", "smtp.password"}; |
|
61 |
+ for(String key : requiredItems) { |
|
62 |
+ if (config.getString(key) == null) { |
|
63 |
+ notConfiguredItems.add(key); |
|
64 |
+ } |
|
65 |
+ } |
|
66 |
+ |
|
67 |
+ String sender = config.getString("smtp.user") + "@" + config.getString("smtp.domain"); |
|
68 |
+ |
|
69 |
+ return ok(massMail.render("title.massMail", notConfiguredItems, sender)); |
|
70 |
+ } |
|
71 |
+ |
|
62 | 72 |
public static Result setting() { |
63 | 73 |
return ok(setting.render("title.siteSetting")); |
64 | 74 |
} |
65 |
- |
|
75 |
+ |
|
66 | 76 |
public static Result userList(int pageNum, String loginId) { |
67 | 77 |
return ok(userList.render("title.siteSetting", User.findUsers(pageNum, loginId))); |
68 | 78 |
} |
69 |
- |
|
79 |
+ |
|
80 |
+ public static Result postList(int pageNum) { |
|
81 |
+ Page<Posting> page = Posting.finder.order("createdDate DESC").findPagingList(30).getPage(pageNum - 1); |
|
82 |
+ return ok(postList.render("title.siteSetting", page)); |
|
83 |
+ } |
|
84 |
+ |
|
85 |
+ public static Result issueList(int pageNum) { |
|
86 |
+ Page<Issue> page = Issue.finder.order("createdDate DESC").findPagingList(30).getPage(pageNum - 1); |
|
87 |
+ return ok(issueList.render("title.siteSetting", page)); |
|
88 |
+ } |
|
89 |
+ |
|
70 | 90 |
public static Result searchUser() { |
71 | 91 |
String loginId = form(User.class).bindFromRequest().get().loginId; |
72 | 92 |
return redirect(routes.SiteApp.userList(0, loginId)); |
73 | 93 |
} |
74 |
- |
|
94 |
+ |
|
75 | 95 |
public static Result deleteUser(Long userId) { |
76 | 96 |
if( User.findByLoginId(session().get("loginId")).isSiteManager() ){ |
77 | 97 |
if(Project.isOnlyManager(userId).size() == 0) |
... | ... | @@ -84,12 +104,12 @@ |
84 | 104 |
|
85 | 105 |
return redirect(routes.SiteApp.userList(0, null)); |
86 | 106 |
} |
87 |
- |
|
107 |
+ |
|
88 | 108 |
public static Result projectList(String filter) { |
89 | 109 |
Page<Project> projects = Project.findByName(filter, 25, 0); |
90 | 110 |
return ok(projectList.render("title.projectList", projects, filter)); |
91 | 111 |
} |
92 |
- |
|
112 |
+ |
|
93 | 113 |
public static Result deleteProject(Long projectId){ |
94 | 114 |
if( User.findByLoginId(session().get("loginId")).isSiteManager() ){ |
95 | 115 |
Project.find.byId(projectId).delete(); |
... | ... | @@ -98,7 +118,7 @@ |
98 | 118 |
} |
99 | 119 |
return redirect(routes.SiteApp.projectList("")); |
100 | 120 |
} |
101 |
- |
|
121 |
+ |
|
102 | 122 |
public static Result softwareMap() { |
103 | 123 |
return TODO; |
104 | 124 |
} |
... | ... | @@ -117,4 +137,43 @@ |
117 | 137 |
flash(Constants.WARNING, "auth.unauthorized.waringMessage"); |
118 | 138 |
return redirect(routes.Application.index()); |
119 | 139 |
} |
140 |
+ |
|
141 |
+ public static Result mailList() { |
|
142 |
+ Set<String> emails = new HashSet<String>(); |
|
143 |
+ Map<String, String[]> projects = request().body().asFormUrlEncoded(); |
|
144 |
+ |
|
145 |
+ if(!UserApp.currentUser().isSiteManager()) { |
|
146 |
+ return forbidden(Messages.get("auth.unauthorized.waringMessage")); |
|
147 |
+ } |
|
148 |
+ |
|
149 |
+ if (!request().accepts("application/json")) { |
|
150 |
+ return status(Http.Status.NOT_ACCEPTABLE); |
|
151 |
+ } |
|
152 |
+ |
|
153 |
+ if (projects == null) { |
|
154 |
+ return ok(toJson(new HashSet<String>())); |
|
155 |
+ } |
|
156 |
+ |
|
157 |
+ if (projects.containsKey("all")) { |
|
158 |
+ if (projects.get("all")[0].equals("true")) { |
|
159 |
+ for(User user : User.find.findList()) { |
|
160 |
+ emails.add(user.email); |
|
161 |
+ } |
|
162 |
+ } |
|
163 |
+ } else { |
|
164 |
+ for(String[] projectNames : projects.values()) { |
|
165 |
+ String projectName = projectNames[0]; |
|
166 |
+ String[] parts = projectName.split("/"); |
|
167 |
+ String owner = parts[0]; |
|
168 |
+ String name = parts[1]; |
|
169 |
+ Project project = Project.findByNameAndOwner(owner, name); |
|
170 |
+ for (ProjectUser projectUser : ProjectUser.findMemberListByProject(project.id)) { |
|
171 |
+ Logger.debug(projectUser.user.email); |
|
172 |
+ emails.add(projectUser.user.email); |
|
173 |
+ } |
|
174 |
+ } |
|
175 |
+ } |
|
176 |
+ |
|
177 |
+ return ok(toJson(emails)); |
|
178 |
+ } |
|
120 | 179 |
} |
--- app/controllers/TagApp.java
+++ app/controllers/TagApp.java
... | ... | @@ -31,9 +31,9 @@ |
31 | 31 |
response().setHeader("Content-Range", "items " + MAX_FETCH_TAGS + "/" + total); |
32 | 32 |
} |
33 | 33 |
|
34 |
- Map<Long, String> tags = new HashMap<Long, String>(); |
|
34 |
+ List<String> tags = new ArrayList<String>(); |
|
35 | 35 |
for (Tag tag: el.findList()) { |
36 |
- tags.put(tag.id, tag.name); |
|
36 |
+ tags.add(tag.name); |
|
37 | 37 |
} |
38 | 38 |
|
39 | 39 |
return ok(toJson(tags)); |
--- app/models/AbstractPosting.java
+++ app/models/AbstractPosting.java
... | ... | @@ -13,6 +13,7 @@ |
13 | 13 |
import javax.persistence.*; |
14 | 14 |
import javax.validation.constraints.Size; |
15 | 15 |
import java.util.Date; |
16 |
+import java.util.List; |
|
16 | 17 |
|
17 | 18 |
import static com.avaje.ebean.Expr.contains; |
18 | 19 |
|
... | ... | @@ -127,7 +128,12 @@ |
127 | 128 |
authorName = user.name; |
128 | 129 |
} |
129 | 130 |
|
131 |
+ abstract public List<? extends Comment> getComments(); |
|
132 |
+ |
|
130 | 133 |
public void delete() { |
134 |
+ for (Comment comment: getComments()) { |
|
135 |
+ comment.delete(); |
|
136 |
+ } |
|
131 | 137 |
Attachment.deleteAll(asResource().getType(), id); |
132 | 138 |
super.delete(); |
133 | 139 |
} |
--- app/models/Attachment.java
+++ app/models/Attachment.java
... | ... | @@ -88,6 +88,12 @@ |
88 | 88 |
.eq("containerId", containerId).findList(); |
89 | 89 |
} |
90 | 90 |
|
91 |
+ public static int countByContainer(ResourceType containerType, Long containerId) { |
|
92 |
+ return find.where() |
|
93 |
+ .eq("containerType", containerType) |
|
94 |
+ .eq("containerId", containerId).findRowCount(); |
|
95 |
+ } |
|
96 |
+ |
|
91 | 97 |
public static List<Attachment> findByContainer(Resource resource) { |
92 | 98 |
return findByContainer(resource.getType(), resource.getId()); |
93 | 99 |
} |
--- app/models/Comment.java
+++ app/models/Comment.java
... | ... | @@ -53,6 +53,7 @@ |
53 | 53 |
} |
54 | 54 |
|
55 | 55 |
public void delete() { |
56 |
+ Attachment.deleteAll(asResource().getType(), id); |
|
56 | 57 |
super.delete(); |
57 | 58 |
getParent().save(); |
58 | 59 |
} |
--- app/models/Issue.java
+++ app/models/Issue.java
... | ... | @@ -104,18 +104,16 @@ |
104 | 104 |
} |
105 | 105 |
|
106 | 106 |
/** |
107 |
- * JXL 라이브러리를 이용하여 엑셀 파일로 저장하며, 해당 파일이 저장된 주소를 반환한다. |
|
107 |
+ * Generate a Microsoft Excel file in byte array from the given issue list, |
|
108 |
+ * using JXL. |
|
108 | 109 |
* |
109 |
- * @param resultList 엑셀로 저장하고자 하는 리스트 |
|
110 |
- * @param pageName 엑셀로 저장하고자 하는 목록의 페이지(내용, ex 이슈, 게시물 등) 이름 |
|
110 |
+ * @param issueList 엑셀로 저장하고자 하는 리스트 |
|
111 | 111 |
* @return |
112 | 112 |
* @throws WriteException |
113 | 113 |
* @throws IOException |
114 | 114 |
* @throws Exception |
115 | 115 |
*/ |
116 |
- public static File excelSave(List<Issue> resultList, String pageName) throws WriteException, IOException { |
|
117 |
- String excelFile = pageName + "_" + JodaDateUtil.today().getTime() + ".xls"; |
|
118 |
- String fullPath = "public/uploadFiles/" + excelFile; |
|
116 |
+ public static byte[] excelFrom(List<Issue> issueList) throws WriteException, IOException { |
|
119 | 117 |
WritableWorkbook workbook = null; |
120 | 118 |
WritableSheet sheet = null; |
121 | 119 |
|
... | ... | @@ -131,7 +129,8 @@ |
131 | 129 |
cf2.setBorder(Border.ALL, BorderLineStyle.THIN); |
132 | 130 |
cf2.setAlignment(Alignment.CENTRE); |
133 | 131 |
|
134 |
- workbook = Workbook.createWorkbook(new File(fullPath)); |
|
132 |
+ ByteArrayOutputStream bos = new ByteArrayOutputStream(); |
|
133 |
+ workbook = Workbook.createWorkbook(bos); |
|
135 | 134 |
sheet = workbook.createSheet(String.valueOf(JodaDateUtil.today().getTime()), 0); |
136 | 135 |
|
137 | 136 |
String[] labalArr = {"ID", "STATE", "TITLE", "ASSIGNEE", "DATE"}; |
... | ... | @@ -140,8 +139,8 @@ |
140 | 139 |
sheet.addCell(new Label(i, 0, labalArr[i], cf1)); |
141 | 140 |
sheet.setColumnView(i, 20); |
142 | 141 |
} |
143 |
- for (int i = 1; i < resultList.size() + 1; i++) { |
|
144 |
- Issue issue = resultList.get(i - 1); |
|
142 |
+ for (int i = 1; i < issueList.size() + 1; i++) { |
|
143 |
+ Issue issue = issueList.get(i - 1); |
|
145 | 144 |
int colcnt = 0; |
146 | 145 |
sheet.addCell(new Label(colcnt++, i, issue.id.toString(), cf2)); |
147 | 146 |
sheet.addCell(new Label(colcnt++, i, issue.state.toString(), cf2)); |
... | ... | @@ -159,7 +158,8 @@ |
159 | 158 |
} catch (IOException e) { |
160 | 159 |
e.printStackTrace(); |
161 | 160 |
} |
162 |
- return new File(fullPath); |
|
161 |
+ |
|
162 |
+ return bos.toByteArray(); |
|
163 | 163 |
} |
164 | 164 |
|
165 | 165 |
/** |
... | ... | @@ -224,5 +224,11 @@ |
224 | 224 |
.findPagingList(size).getPage(0) |
225 | 225 |
.getList(); |
226 | 226 |
} |
227 |
+ |
|
228 |
+ @Transient |
|
229 |
+ public List<? extends Comment> getComments() { |
|
230 |
+ return comments; |
|
231 |
+ } |
|
232 |
+ |
|
227 | 233 |
} |
228 | 234 |
|
--- app/models/Milestone.java
+++ app/models/Milestone.java
... | ... | @@ -58,11 +58,11 @@ |
58 | 58 |
} |
59 | 59 |
|
60 | 60 |
public int getNumClosedIssues() { |
61 |
- return Issue.finder.where().eq("milestone", this).eq("state", State.CLOSED).findRowCount(); |
|
61 |
+ return Issue.finder.where().eq("milestone", this).eq("state", State.CLOSED).findRowCount(); |
|
62 | 62 |
} |
63 | 63 |
|
64 | 64 |
public int getNumOpenIssues() { |
65 |
- return Issue.finder.where().eq("milestone", this).eq("state", State.OPEN).findRowCount(); |
|
65 |
+ return Issue.finder.where().eq("milestone", this).eq("state", State.OPEN).findRowCount(); |
|
66 | 66 |
} |
67 | 67 |
|
68 | 68 |
public int getNumTotalIssues() { |
... | ... | @@ -139,13 +139,38 @@ |
139 | 139 |
* @return |
140 | 140 |
*/ |
141 | 141 |
public static List<Milestone> findMilestones(Long projectId, |
142 |
- State state, String sort, Direction direction) { |
|
143 |
- OrderParams orderParams = new OrderParams().add(sort, direction); |
|
142 |
+ State state, String sort, final Direction direction) { |
|
143 |
+ |
|
144 |
+ OrderParams orderParams = new OrderParams(); |
|
145 |
+ |
|
146 |
+ if(!"completionRate".equals(sort)) { |
|
147 |
+ orderParams.add(sort, direction); |
|
148 |
+ } |
|
149 |
+ |
|
144 | 150 |
SearchParams searchParams = new SearchParams().add("project.id", projectId, Matching.EQUALS); |
145 |
- if (state != null && state != State.ALL) { |
|
151 |
+ if(state != null && state != State.ALL) { |
|
146 | 152 |
searchParams.add("state", state, Matching.EQUALS); |
147 | 153 |
} |
148 |
- return FinderTemplate.findBy(orderParams, searchParams, find); |
|
154 |
+ |
|
155 |
+ List<Milestone> milestones = FinderTemplate.findBy(orderParams, searchParams, find); |
|
156 |
+ |
|
157 |
+ if("completionRate".equals(sort)) { |
|
158 |
+ Collections.sort(milestones, new Comparator<Milestone>() { |
|
159 |
+ @Override |
|
160 |
+ public int compare(Milestone o1, Milestone o2) { |
|
161 |
+ int o1CompletionRate = o1.getCompletionRate(); |
|
162 |
+ int o2CompletionRate = o2.getCompletionRate(); |
|
163 |
+ |
|
164 |
+ if(direction == Direction.ASC) { |
|
165 |
+ return (o1CompletionRate < o2CompletionRate ? -1 : (o1CompletionRate == o2CompletionRate ? 0 : 1)); |
|
166 |
+ } else { |
|
167 |
+ return (o1CompletionRate < o2CompletionRate ? 1 : (o1CompletionRate == o2CompletionRate ? 0 : -1)); |
|
168 |
+ } |
|
169 |
+ } |
|
170 |
+ }); |
|
171 |
+ } |
|
172 |
+ |
|
173 |
+ return milestones; |
|
149 | 174 |
} |
150 | 175 |
|
151 | 176 |
public void updateWith(Milestone newMilestone) { |
--- app/models/Posting.java
+++ app/models/Posting.java
... | ... | @@ -61,4 +61,9 @@ |
61 | 61 |
.findPagingList(size).getPage(0) |
62 | 62 |
.getList(); |
63 | 63 |
} |
64 |
+ |
|
65 |
+ @Transient |
|
66 |
+ public List<? extends Comment> getComments() { |
|
67 |
+ return comments; |
|
68 |
+ } |
|
64 | 69 |
} |
--- app/models/Project.java
+++ app/models/Project.java
... | ... | @@ -1,6 +1,7 @@ |
1 | 1 |
package models; |
2 | 2 |
|
3 | 3 |
import java.io.IOException; |
4 |
+import java.text.SimpleDateFormat; |
|
4 | 5 |
import java.util.*; |
5 | 6 |
|
6 | 7 |
import javax.persistence.*; |
... | ... | @@ -77,6 +78,11 @@ |
77 | 78 |
@ManyToMany |
78 | 79 |
public Set<Tag> tags; |
79 | 80 |
|
81 |
+ public String getCreatedDate() { |
|
82 |
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd"); |
|
83 |
+ return sdf.format(this.createdDate); |
|
84 |
+ } |
|
85 |
+ |
|
80 | 86 |
public static Long create(Project newProject) { |
81 | 87 |
newProject.siteurl = "http://localhost:9000/" + newProject.name; |
82 | 88 |
newProject.createdDate = new Date(); |
--- app/utils/HttpUtil.java
+++ app/utils/HttpUtil.java
... | ... | @@ -1,7 +1,12 @@ |
1 | 1 |
package utils; |
2 | 2 |
|
3 |
+import play.api.http.MediaRange; |
|
4 |
+import play.mvc.Http; |
|
5 |
+import scala.Option; |
|
6 |
+ |
|
3 | 7 |
import java.io.UnsupportedEncodingException; |
4 | 8 |
import java.net.URLEncoder; |
9 |
+import java.util.List; |
|
5 | 10 |
import java.util.Map; |
6 | 11 |
|
7 | 12 |
public class HttpUtil { |
... | ... | @@ -25,4 +30,16 @@ |
25 | 30 |
filename = "filename*=UTF-8''" + filename; |
26 | 31 |
return filename; |
27 | 32 |
} |
33 |
+ |
|
34 |
+ public static String getPreferType(Http.Request request, String ... types) { |
|
35 |
+ // acceptedTypes is sorted by preference. |
|
36 |
+ for(MediaRange range : request.acceptedTypes()) { |
|
37 |
+ for(String type : types) { |
|
38 |
+ if (range.accepts(type)) { |
|
39 |
+ return type; |
|
40 |
+ } |
|
41 |
+ } |
|
42 |
+ } |
|
43 |
+ return null; |
|
44 |
+ } |
|
28 | 45 |
} |
--- app/views/board/editPost.scala.html
+++ app/views/board/editPost.scala.html
... | ... | @@ -46,7 +46,7 @@ |
46 | 46 |
<div class="avatar-wrap"> |
47 | 47 |
<img src="@User.findByLoginId(session.get("loginId")).avatarUrl" class="img-rounded" width="32" height="32" alt="avatar"> |
48 | 48 |
</div> |
49 |
- <div id="upload" class="attach-info-wrap" resourceType="@ResourceType.BOARD_POST"> |
|
49 |
+ <div id="upload" class="attach-info-wrap" resourceType="@ResourceType.BOARD_POST" resourceId="@postId"> |
|
50 | 50 |
<div> |
51 | 51 |
<span class="progress-num">0%</span> <span class="sp-line"> </span> |
52 | 52 |
<strong>total</strong> <span class="total-num">0MB</span> |
--- app/views/layout.scala.html
+++ app/views/layout.scala.html
... | ... | @@ -9,7 +9,7 @@ |
9 | 9 |
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> |
10 | 10 |
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> |
11 | 11 |
<link rel="shortcut icon" type="image/png" href="@routes.Assets.at("images/favicon.ico")"> |
12 |
-<link rel="stylesheet" type="text/css" media="screen" href="@getCSSLink("bootstrap.min")"> |
|
12 |
+<link rel="stylesheet" type="text/css" media="screen" href="@getCSSLink("bootstrap")"> |
|
13 | 13 |
<link rel="stylesheet" type="text/css" media="screen" href="@getCSSLink("nforge")"> |
14 | 14 |
<script type="text/javascript" src="@getJSLink("lib/jquery/jquery-1.9.0")"></script> |
15 | 15 |
<script type="text/javascript" src="@getJSLink("common/hive.Common")"></script> |
... | ... | @@ -76,5 +76,17 @@ |
76 | 76 |
<!-- HIVE --> |
77 | 77 |
@uservoice("196721") |
78 | 78 |
|
79 |
+ <script type="text/javascript"> |
|
80 |
+ var _gaq = _gaq || []; |
|
81 |
+ _gaq.push(['_setAccount', 'UA-40528193-1']); |
|
82 |
+ _gaq.push(['_trackPageview']); |
|
83 |
+ |
|
84 |
+ (function() { |
|
85 |
+ var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; |
|
86 |
+ ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; |
|
87 |
+ var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); |
|
88 |
+ })(); |
|
89 |
+ |
|
90 |
+ </script> |
|
79 | 91 |
</body> |
80 | 92 |
</html>(파일 끝에 줄바꿈 문자 없음) |
--- app/views/login.scala.html
+++ app/views/login.scala.html
... | ... | @@ -26,7 +26,7 @@ |
26 | 26 |
</dl> |
27 | 27 |
|
28 | 28 |
<div class="act-row right-txt"> |
29 |
- <input type="checkbox" class="checkbox" autocomplete="off" id="remember-me" name="remeber-me"><label for="remember-me" class="bg-checkbox">@Messages("title.rememberMe")</label> |
|
29 |
+ <input type="checkbox" class="checkbox" autocomplete="off" id="remember-me" name="rememberMe"><label for="remember-me" class="bg-checkbox">@Messages("title.rememberMe")</label> |
|
30 | 30 |
<a href="@routes.PasswordResetApp.lostPassword()" class="forgot-password"><span class="label ">@Messages("title.forgotpassword")</span></a> |
31 | 31 |
</div> |
32 | 32 |
|
--- app/views/milestone/list.scala.html
+++ app/views/milestone/list.scala.html
... | ... | @@ -65,9 +65,9 @@ |
65 | 65 |
<a href="@makeSortLink("completionRate")" class="filter active"><i class="ico ico-sort @sortMark("completionRate")"></i>@Messages("order.completionRate")</a> |
66 | 66 |
</div> |
67 | 67 |
<div class="legend"> |
68 |
- <i class="orange"></i>완료 |
|
69 |
- <i class="blue"></i>진행중 |
|
70 |
- <i class="gray"></i>미할당 |
|
68 |
+ <i class="gray"></i>@Messages("issue.state.open") |
|
69 |
+ <i class="blue"></i>@Messages("issue.state.assigned") |
|
70 |
+ <i class="orange"></i>@Messages("issue.state.closed") |
|
71 | 71 |
</div> |
72 | 72 |
</div> |
73 | 73 |
|
... | ... | @@ -102,7 +102,7 @@ |
102 | 102 |
<strong class="version"></strong> |
103 | 103 |
<span class="title">@milestone.title</span> |
104 | 104 |
<span class="sp">|</span> |
105 |
- <span class="due-date">완료일 <strong>@milestone.getDueDateString</strong></span> |
|
105 |
+ <span class="due-date">@Messages("label.dueDate") <strong>@milestone.getDueDateString</strong></span> |
|
106 | 106 |
</div> |
107 | 107 |
|
108 | 108 |
<div class="progress-wrap"> |
... | ... | @@ -115,15 +115,10 @@ |
115 | 115 |
<div class="progress-label"> |
116 | 116 |
@** 0% 일 수도 있어서 최소한의 텍스트 영역 확보 **@ |
117 | 117 |
<div class="pull-left" style="width: @milestone.getCompletionRate%; min-width:100px;"> |
118 |
- <a href="@makeIssuesLink(milestone.id,"closed")">완료 <strong>@milestone.getCompletionRate%</strong></a> |
|
118 |
+ <a href="@makeIssuesLink(milestone.id,"closed")">@Messages("milestone.state.closed") <strong>@milestone.getCompletionRate%</strong></a> |
|
119 | 119 |
</div> |
120 |
- <!-- |
|
121 |
- <div class="pull-left center-txt" style="width: 30%;"> |
|
122 |
- 진행중 <strong>30%</strong> |
|
123 |
- </div> |
|
124 |
- --> |
|
125 | 120 |
<div class="pull-right"> |
126 |
- <a href="@makeIssuesLink(milestone.id,"open")">미완료 <strong>@{100 - milestone.getCompletionRate}%</strong></a> |
|
121 |
+ <a href="@makeIssuesLink(milestone.id,"open")">@Messages("issue.state.open") <strong>@{100 - milestone.getCompletionRate}%</strong></a> |
|
127 | 122 |
</div> |
128 | 123 |
</div> |
129 | 124 |
</div> |
--- app/views/milestone/manage.scala.html
+++ app/views/milestone/manage.scala.html
... | ... | @@ -55,7 +55,7 @@ |
55 | 55 |
<td><a href="@makeSortLink("title")">@Messages("label.title") @sortMark("title")</a></td> |
56 | 56 |
<td>@Messages("label.contents")</td> |
57 | 57 |
<td><a href="@makeSortLink("dueDate")">@Messages("label.dueDate") @sortMark("dueDate")</a></td> |
58 |
- <td><a href="@makeSortLink("completionRate")">@Messages("label.state") @sortMark("completionRate")</a></td> |
|
58 |
+ <td><a href="@makeSortLink("state")">@Messages("label.state") @sortMark("state")</a></td> |
|
59 | 59 |
<td> </td> |
60 | 60 |
</tr> |
61 | 61 |
</thead> |
--- app/views/project/projectList.scala.html
+++ app/views/project/projectList.scala.html
... | ... | @@ -1,4 +1,5 @@ |
1 | 1 |
@(message: String, currentPage: com.avaje.ebean.Page[Project], filter:String, listState:String) |
2 |
+@import utils.TemplateHelper._ |
|
2 | 3 |
|
3 | 4 |
@home(message, utils.MenuType.PROJECTS) { |
4 | 5 |
<div class="page"> |
... | ... | @@ -18,7 +19,7 @@ |
18 | 19 |
<input name="state" type="hidden" value="@listState"> |
19 | 20 |
<input name="filter" class="text" type="text" placeholder="@Messages("site.project.filter")" value="@filter"><!-- |
20 | 21 |
--><button type="submit" class="btn search-btn">@Messages("issue.search")</button><!-- |
21 |
- <a href="#" class="btn-advanced"><i class="ico"></i>Advanced Search</a> --> |
|
22 |
+ <a href="#" class=tn-advanced"><i class="ico"></i>Advanced Search</a> --> |
|
22 | 23 |
|
23 | 24 |
<div class="srch-advanced"> |
24 | 25 |
<!-- TODO: fill this form --> |
... | ... | @@ -42,7 +43,7 @@ |
42 | 43 |
<div class="desc"> |
43 | 44 |
@project.overview |
44 | 45 |
</div> |
45 |
- <p class="name-tag">by <a href="@routes.UserApp.userInfo(project.owner)">@project.owner</a> <!--<span>20 days ago.</span>--></p> |
|
46 |
+ <p class="name-tag">by <a href="@routes.UserApp.userInfo(project.owner)"><b>@project.owner</b></a> since @agoString(project.ago)</p> |
|
46 | 47 |
</div> |
47 | 48 |
<div class="stats-wrap"> |
48 | 49 |
<div class="like"> |
+++ app/views/site/issueList.scala.html
... | ... | @@ -0,0 +1,40 @@ |
1 | +@(message: String, currentPage: com.avaje.ebean.Page[Issue]) | |
2 | + | |
3 | +@siteMngMain(message) { | |
4 | + <h2>@Messages("site.sidebar.issueList")</h2> | |
5 | + | |
6 | + <div class="row-fluid"> | |
7 | + <table class="table table-striped table-condensed"> | |
8 | + <thead> | |
9 | + <tr> | |
10 | + <th>@Messages("project.name")</th> | |
11 | + <th>@Messages("issue.author")</th> | |
12 | + <th>@Messages("issue.title")</th> | |
13 | + <th>@Messages("issue.createdDate")</th> | |
14 | + <th>@Messages("issue.attachment")</th> | |
15 | + <th>@Messages("issue.numOfComments")</th> | |
16 | + </tr> | |
17 | + </thead> | |
18 | + <tbody> | |
19 | + @for(issue <- currentPage.getList()) { | |
20 | + @issuelisting(issue) | |
21 | + } | |
22 | + </tbody> | |
23 | + </table> | |
24 | + </div> | |
25 | + | |
26 | + <div class="row-fluid"> | |
27 | + <center>@pagination(currentPage, 5, "pagination", "/sites/issueList")</center> | |
28 | + </div> | |
29 | +} | |
30 | + | |
31 | +@issuelisting(issue: models.Issue) = { | |
32 | + <tr> | |
33 | + <td>@issue.project.name</td> | |
34 | + <td>@issue.authorName</td> | |
35 | + <td><a href="@routes.BoardApp.post(issue.project.owner, issue.project.name, issue.id)">@issue.title</a></td> | |
36 | + <td>@utils.TemplateHelper.agoString(issue.ago())</td> | |
37 | + <td>@models.Attachment.countByContainer(models.enumeration.ResourceType.ISSUE_POST, issue.id)</td> | |
38 | + <td>@issue.numOfComments</td> | |
39 | + </tr> | |
40 | +}(파일 끝에 줄바꿈 문자 없음) |
+++ app/views/site/massMail.scala.html
... | ... | @@ -0,0 +1,42 @@ |
1 | +@(message: String, notConfiguredItems: List[String], sender: String) | |
2 | + | |
3 | +@siteMngMain(message) { | |
4 | +<div class="page-header"> | |
5 | + <h3>@Messages(message)</h3> | |
6 | +</div> | |
7 | + | |
8 | +<div> | |
9 | + <p> | |
10 | + <label class="radio"> | |
11 | + <input type="radio" name="mailingType" value="all"> | |
12 | + @Messages("site.massMail.toAll") | |
13 | + </label> | |
14 | + </p> | |
15 | + <p> | |
16 | + <label class="radio"> | |
17 | + <input type="radio" name="mailingType" value="projects" checked>@Messages("site.massMail.toProjects"): | |
18 | + <span id="selected-projects"><!-- project names are added here by hive.site.MassMail.js --></span> | |
19 | + <input type="text" class="text uname" | |
20 | + id="input-project" | |
21 | + data-provider="typeahead" autocomplete="off" | |
22 | + placeholder="@Messages("project.name")"/> | |
23 | + <button type="submit" class="btn btn-primary" id="select-project" data-loading-text="@Messages("site.massMail.loading")"> | |
24 | + <strong>@Messages("button.add")</strong> | |
25 | + </button> | |
26 | + </label> | |
27 | + </p> | |
28 | +</div> | |
29 | + | |
30 | +<button type="submit" class="btn btn-primary" id="write-email"> | |
31 | + <strong>@Messages("mail.write")</strong> | |
32 | +</button> | |
33 | + | |
34 | +<script type="text/javascript"> | |
35 | + $(document).ready(function(){ | |
36 | + $hive.loadModule("site.MassMail", { | |
37 | + "sURLMailList": "@routes.SiteApp.mailList()", | |
38 | + "sURLProjects": "@routes.ProjectApp.projects()" | |
39 | + }); | |
40 | + }); | |
41 | +</script> | |
42 | +} |
+++ app/views/site/pagination.scala.html
... | ... | @@ -0,0 +1,46 @@ |
1 | +@(page:com.avaje.ebean.Page[_ <: play.db.ebean.Model], pageNum:Int, divId:String, listUrl:String) | |
2 | + | |
3 | +@{ | |
4 | + var currentPageNum = page.getPageIndex + 1 | |
5 | + var lastPageNum = page.getTotalPageCount() | |
6 | + var str = "" | |
7 | + | |
8 | + if(currentPageNum <= pageNum/2) { | |
9 | + currentPageNum = pageNum/2 +1 | |
10 | + } else if(currentPageNum > lastPageNum - pageNum/2) { | |
11 | + currentPageNum = lastPageNum - pageNum/2 - 1 | |
12 | + } | |
13 | + makeList(currentPageNum) | |
14 | +} | |
15 | +@makeList(currentPageNum:Int) = { | |
16 | + <div class='pagination' id="@divId"><ul> | |
17 | + @if(page.hasPrev){ | |
18 | + @makeLink("Prev", page.getPageIndex) | |
19 | + } else { | |
20 | + <li class="disabled"><a>Prev</a></li> | |
21 | + } | |
22 | + | |
23 | + @if(page.getTotalPageCount() < pageNum) { | |
24 | + @for(x <- (1 to page.getTotalPageCount())){ | |
25 | + @makeLink(x + "", x) | |
26 | + } | |
27 | + } else { | |
28 | + @for( x <- (currentPageNum - pageNum/2 to currentPageNum + pageNum/2)){ | |
29 | + @makeLink(x + "", x) | |
30 | + } | |
31 | + } | |
32 | + | |
33 | + | |
34 | + @if(page.hasNext) { | |
35 | + @makeLink("Next",page.getPageIndex + 2) | |
36 | + } else { | |
37 | + <li class="disabled"><a>Next</a></li> | |
38 | + } | |
39 | + | |
40 | + </ul></div> | |
41 | +} | |
42 | +@makeLink(title:String, index:Int) = { | |
43 | + <li class="@if((page.getPageIndex + 1).equals(index)){active}"> | |
44 | + <a href="@(listUrl + "?pageNum=" + index)" pageNum="@index">@title</a> | |
45 | + </li> | |
46 | +}(파일 끝에 줄바꿈 문자 없음) |
+++ app/views/site/postList.scala.html
... | ... | @@ -0,0 +1,40 @@ |
1 | +@(message: String, currentPage: com.avaje.ebean.Page[Posting]) | |
2 | + | |
3 | +@siteMngMain(message) { | |
4 | + <h2>@Messages("site.sidebar.postList")</h2> | |
5 | + | |
6 | + <div class="row-fluid"> | |
7 | + <table class="table table-striped table-condensed"> | |
8 | + <thead> | |
9 | + <tr> | |
10 | + <th>@Messages("project.name")</th> | |
11 | + <th>@Messages("post.author")</th> | |
12 | + <th>@Messages("post.title")</th> | |
13 | + <th>@Messages("post.createdDate")</th> | |
14 | + <th>@Messages("post.attachment")</th> | |
15 | + <th>@Messages("post.numOfComments")</th> | |
16 | + </tr> | |
17 | + </thead> | |
18 | + <tbody> | |
19 | + @for(post <- currentPage.getList()) { | |
20 | + @postlisting(post) | |
21 | + } | |
22 | + </tbody> | |
23 | + </table> | |
24 | + </div> | |
25 | + | |
26 | + <div class="row-fluid"> | |
27 | + <center>@pagination(currentPage, 5, "pagination", "/sites/postList")</center> | |
28 | + </div> | |
29 | +} | |
30 | + | |
31 | +@postlisting(post: models.Posting) = { | |
32 | + <tr> | |
33 | + <td>@post.project.name</td> | |
34 | + <td>@post.authorName</td> | |
35 | + <td><a href="@routes.BoardApp.post(post.project.owner, post.project.name, post.id)">@post.title</a></td> | |
36 | + <td>@utils.TemplateHelper.agoString(post.ago())</td> | |
37 | + <td>@models.Attachment.countByContainer(models.enumeration.ResourceType.BOARD_POST, post.id)</td> | |
38 | + <td>@post.numOfComments</td> | |
39 | + </tr> | |
40 | +}(파일 끝에 줄바꿈 문자 없음) |
--- app/views/site/projectList.scala.html
+++ app/views/site/projectList.scala.html
... | ... | @@ -7,7 +7,7 @@ |
7 | 7 |
<button type="submit" class="btn">검색</button> |
8 | 8 |
</form> |
9 | 9 |
</div> |
10 |
- |
|
10 |
+ |
|
11 | 11 |
<div class="row-fluid"> |
12 | 12 |
<table class="table table-striped table-condensed"> |
13 | 13 |
<thead> |
... | ... | @@ -24,9 +24,9 @@ |
24 | 24 |
<td>@project.owner</td> |
25 | 25 |
<td> |
26 | 26 |
<a class="btn btn-danger" data-toggle="modal" href="#alertDeletion@project.id">@Messages("button.delete")</a> |
27 |
- </td> |
|
27 |
+ </td> |
|
28 | 28 |
</tr> |
29 |
- |
|
29 |
+ |
|
30 | 30 |
<div class="modal hide" id="alertDeletion@project.id"> |
31 | 31 |
<div class="modal-header"> |
32 | 32 |
<button type="button" class="close" data-dismiss="modal">×</button> |
... | ... | @@ -44,7 +44,7 @@ |
44 | 44 |
</tbody> |
45 | 45 |
</table> |
46 | 46 |
</div> |
47 |
- |
|
47 |
+ |
|
48 | 48 |
<div class="row-fluid"> |
49 | 49 |
<center>@paginationForUserList(currentPage, 5, "pagination")</center> |
50 | 50 |
</div> |
--- app/views/user/info.scala.html
+++ app/views/user/info.scala.html
... | ... | @@ -86,12 +86,6 @@ |
86 | 86 |
</div> |
87 | 87 |
</div> |
88 | 88 |
</div> |
89 |
- <div class="filter-wrap user-setting"> |
|
90 |
- <div class="filters"> |
|
91 |
- <a href="#" class="filter"><i class="ico btn-gray-arrow"></i>@Messages("order.all")</a> |
|
92 |
- <a href="#" class="filter active"><i class="ico btn-gray-arrow down"></i>@Messages("order.state")</a> |
|
93 |
- </div> |
|
94 |
- </div> |
|
95 | 89 |
</div> |
96 | 90 |
<ul class="user-streams all-projects"> |
97 | 91 |
@for(project <- user.myProjects()){ |
--- conf/logger.xml
... | ... | @@ -1,23 +0,0 @@ |
1 | -<configuration> | |
2 | - | |
3 | - <conversionRule conversionWord="coloredLevel" converterClass="play.api.Logger$ColoredLevel" /> | |
4 | - | |
5 | - <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> | |
6 | - <encoder> | |
7 | - <pattern>%date - [%level] - from %logger in %thread %n%message%n%xException%n</pattern> | |
8 | - </encoder> | |
9 | - </appender> | |
10 | - | |
11 | - <logger name="play" level="INFO" /> | |
12 | - <logger name="application" level="INFO" /> | |
13 | - | |
14 | - <!-- Off these ones as they are annoying, and anyway we manage configuration ourself --> | |
15 | - <logger name="com.avaje.ebean.config.PropertyMapLoader" level="OFF" /> | |
16 | - <logger name="com.avaje.ebeaninternal.server.core.XmlConfigLoader" level="OFF" /> | |
17 | - <logger name="com.avaje.ebeaninternal.server.lib.BackgroundThread" level="OFF" /> | |
18 | - | |
19 | - <root level="ERROR"> | |
20 | - <appender-ref ref="STDOUT" /> | |
21 | - </root> | |
22 | -</configuration> | |
23 | - (파일 끝에 줄바꿈 문자 없음) |
--- conf/messages.en
+++ conf/messages.en
... | ... | @@ -62,12 +62,14 @@ |
62 | 62 |
label.confirm.delete = Are you Sure? |
63 | 63 |
label.select = Select Label |
64 | 64 |
label.error.duplicated = Failed to create new label. It might be same label exists already. |
65 |
-label.error.creationFailed = Failed to create new label. It might be server error or your request is invalid. |
|
65 |
+label.error.creationFailed = Failed to create new label. It might be server error or your request is invalid. |
|
66 | 66 |
|
67 | 67 |
order.all = All |
68 | 68 |
order.date = Date |
69 | 69 |
order.state = State |
70 | 70 |
order.comments = Comments |
71 |
+order.completionRate = Completion Rate |
|
72 |
+order.dueDate = Due Date |
|
71 | 73 |
|
72 | 74 |
button.save = Save |
73 | 75 |
button.confirm = Confirm |
... | ... | @@ -116,7 +118,7 @@ |
116 | 118 |
milestone.form.dueDate = Choose due date |
117 | 119 |
milestone.error.title = Title is required |
118 | 120 |
milestone.error.content = Description is required |
119 |
-milestone.error.duedateFormat = Invalid format of due date (YYYY-MM-DD) |
|
121 |
+milestone.error.duedateFormat = Invalid format of due date (YYYY-MM-DD) |
|
120 | 122 |
|
121 | 123 |
#Issue |
122 | 124 |
issue.state.unit = issues |
... | ... | @@ -192,7 +194,11 @@ |
192 | 194 |
issue.downloadAsExcel = Download as Excel |
193 | 195 |
issue.search = SEARCH |
194 | 196 |
issue.error.emptyTitle = Title of this issue is required |
195 |
-issue.error.emptyBody = Description about this issue is required |
|
197 |
+issue.error.emptyBody = Description about this issue is required |
|
198 |
+issue.title = Title |
|
199 |
+issue.createdDate = Created Date |
|
200 |
+issue.attachment = Attachment |
|
201 |
+issue.numOfComments = Comment Count |
|
196 | 202 |
|
197 | 203 |
#Post |
198 | 204 |
post.new = New |
... | ... | @@ -210,6 +216,10 @@ |
210 | 216 |
post.popup.fileAttach.contents = Please, select file to attach |
211 | 217 |
post.edit.rejectNotAuthor = You don't have permisstion to access. |
212 | 218 |
post.update.error = Errors on Input Value |
219 |
+post.title = Title |
|
220 |
+post.createdDate = Created Date |
|
221 |
+post.attachment = Attachment |
|
222 |
+post.numOfComments = Comment Count |
|
213 | 223 |
|
214 | 224 |
#Project |
215 | 225 |
project.myproject = MY PROJECTS |
... | ... | @@ -278,9 +288,12 @@ |
278 | 288 |
site.sidebar = Site Management |
279 | 289 |
site.sidebar.setting = Site Setting |
280 | 290 |
site.sidebar.userList = Users |
291 |
+site.sidebar.postList = Posts |
|
292 |
+site.sidebar.issueList = Issues |
|
281 | 293 |
site.sidebar.projectList = Projects |
282 | 294 |
site.sidebar.softwareMap = Software Map |
283 | 295 |
site.sidebar.mailSend = Send a Email |
296 |
+site.sidebar.massMail = Send Mass Emails |
|
284 | 297 |
site.project.filter = Find Project by Project Name |
285 | 298 |
site.userList.search = Find a user by his/her login ID... |
286 | 299 |
site.userList.isActive = Active Status |
... | ... | @@ -291,6 +304,9 @@ |
291 | 304 |
site.project.deleteConfirm = Do you want to delete this project? |
292 | 305 |
site.userlist.unlocked = Active User List |
293 | 306 |
site.userlist.locked = Account Locked User List |
307 |
+site.massMail.toAll = To All |
|
308 |
+site.massMail.toProjects = To members of specific projects |
|
309 |
+site.massMail.loading = Loading... |
|
294 | 310 |
|
295 | 311 |
#User |
296 | 312 |
user.loginId = Login ID |
... | ... | @@ -359,7 +375,7 @@ |
359 | 375 |
code.commits = Commits |
360 | 376 |
code.newer = Newer |
361 | 377 |
code.older = Older |
362 |
-code.repoUrl = REPOSITORY URL |
|
378 |
+code.repoUrl = URL |
|
363 | 379 |
|
364 | 380 |
|
365 | 381 |
mail.password = Password |
... | ... | @@ -384,6 +400,7 @@ |
384 | 400 |
mail.subject = Subject |
385 | 401 |
mail.body = Body |
386 | 402 |
mail.send = Send |
403 |
+mail.write = Write |
|
387 | 404 |
mail.fail = Failed to send a mail. |
388 | 405 |
mail.sended = Mail is sent. |
389 | 406 |
|
... | ... | @@ -393,7 +410,7 @@ |
393 | 410 |
none = None |
394 | 411 |
comment = Comment |
395 | 412 |
noAssignee = No Assignee |
396 |
-noMilestone = No Milestone |
|
413 |
+noMilestone = No Milestone |
|
397 | 414 |
|
398 | 415 |
#validation |
399 | 416 |
validation.tooShortPassword = The password field must be at least 4 characters in length. |
--- conf/messages.ko
+++ conf/messages.ko
... | ... | @@ -22,6 +22,7 @@ |
22 | 22 |
title.commitHistory = 커밋 히스토리 |
23 | 23 |
title.mailSetting = 메일 설정 |
24 | 24 |
title.sendMail = 메일 발송 |
25 |
+title.massMail = 대량 메일 발송 |
|
25 | 26 |
title.projectList = 프로젝트 목록 |
26 | 27 |
title.help = 도움말 |
27 | 28 |
title.search = 검색 |
... | ... | @@ -63,7 +64,7 @@ |
63 | 64 |
label.confirm.delete = 라벨을 삭제하면 이슈에 지정한 라벨도 함께 제거됩니다. 정말 삭제하시겠습니까? |
64 | 65 |
label.select = 라벨 선택 |
65 | 66 |
label.error.duplicated = 라벨 생성에 실패했습니다.\n이미 동일한 라벨이 존재할지도 모릅니다. |
66 |
-label.error.creationFailed = 라벨 생성에 실패했습니다.\n서버에 문제가 있거나 올바른 요청이 아닐 수 있습니다. |
|
67 |
+label.error.creationFailed = 라벨 생성에 실패했습니다.\n서버에 문제가 있거나 올바른 요청이 아닐 수 있습니다. |
|
67 | 68 |
|
68 | 69 |
order.all = 전체 |
69 | 70 |
order.date = 날짜순 |
... | ... | @@ -94,8 +95,8 @@ |
94 | 95 |
button.apply = 적용 |
95 | 96 |
button.back = 돌아가기 |
96 | 97 |
button.upload = 파일 올리기 |
97 |
-button.user.makeAccountUnlock.true = 잠김해제 |
|
98 |
-button.user.makeAccountUnlock.false = 계정잠그기 |
|
98 |
+button.user.makeAccountUnlock.true = 잠김해제 |
|
99 |
+button.user.makeAccountUnlock.false = 계정잠그기 |
|
99 | 100 |
|
100 | 101 |
checkbox.commented = 댓글 |
101 | 102 |
checkbox.fileAttached = 첨부파일 |
... | ... | @@ -120,7 +121,7 @@ |
120 | 121 |
milestone.form.dueDate = 완료일을 선택하세요 |
121 | 122 |
milestone.error.title = 마일스톤 제목을 입력해주세요 |
122 | 123 |
milestone.error.content = 마일스톤 내용을 입력해주세요 |
123 |
-milestone.error.duedateFormat = 완료일 형식이 잘못되었습니다. YYYY-MM-DD 형식으로 입력해주세요. |
|
124 |
+milestone.error.duedateFormat = 완료일 형식이 잘못되었습니다. YYYY-MM-DD 형식으로 입력해주세요. |
|
124 | 125 |
|
125 | 126 |
#Issue |
126 | 127 |
issue.state.unit = 이슈 |
... | ... | @@ -197,7 +198,11 @@ |
197 | 198 |
issue.downloadAsExcel = 엑셀파일로 다운받기 |
198 | 199 |
issue.search = 검색 |
199 | 200 |
issue.error.emptyTitle = 이슈 제목을 입력해주세요 |
200 |
-issue.error.emptyBody = 이슈 내용을 입력해주세요 |
|
201 |
+issue.error.emptyBody = 이슈 내용을 입력해주세요 |
|
202 |
+issue.title = 제목 |
|
203 |
+issue.createdDate = 작성일 |
|
204 |
+issue.attachment = 첨부파일 |
|
205 |
+issue.numOfComments = 댓글개수 |
|
201 | 206 |
|
202 | 207 |
#Post |
203 | 208 |
post.new = 새글 |
... | ... | @@ -215,6 +220,10 @@ |
215 | 220 |
post.popup.fileAttach.contents = 첨부할 파일을 선택해주세요. |
216 | 221 |
post.edit.rejectNotAuthor = 글쓴이가 아닙니다. |
217 | 222 |
post.update.error = 입력값 오류 |
223 |
+post.title = 제목 |
|
224 |
+post.createdDate = 작성일 |
|
225 |
+post.attachment = 첨부파일 |
|
226 |
+post.numOfComments = 댓글개수 |
|
218 | 227 |
|
219 | 228 |
#Project |
220 | 229 |
project.myproject = 내 프로젝트 |
... | ... | @@ -249,7 +258,7 @@ |
249 | 258 |
project.member.delete = 프로젝트 유저 삭제 |
250 | 259 |
project.member.notExist = 존재하지 않는 유저입니다. |
251 | 260 |
project.member.ownerCannotLeave = 프로젝트 소유자는 탈퇴할 수 없습니다. |
252 |
-project.member.ownerMustBeAManager = 프로젝트 소유자는 관리 권한을 가져야 합니다. |
|
261 |
+project.member.ownerMustBeAManager = 프로젝트 소유자는 관리 권한을 가져야 합니다. |
|
253 | 262 |
project.sidebar = 프로젝트 설정 |
254 | 263 |
project.setting = 설정 |
255 | 264 |
project.member = 멤버 |
... | ... | @@ -283,10 +292,13 @@ |
283 | 292 |
#Site |
284 | 293 |
site.sidebar = 사이트 관리 |
285 | 294 |
site.sidebar.setting = 설정 |
286 |
-site.sidebar.userList = 유저 관리 |
|
295 |
+site.sidebar.userList = 유저 |
|
296 |
+site.sidebar.postList = 게시물 |
|
297 |
+site.sidebar.issueList = 이슈 |
|
287 | 298 |
site.sidebar.projectList = 프로젝트 설정 |
288 | 299 |
site.sidebar.softwareMap = 소프트웨어 맵 |
289 | 300 |
site.sidebar.mailSend = 메일 발송 |
301 |
+site.sidebar.massMail = 대량 메일 발송 |
|
290 | 302 |
site.project.filter = 찾으려는 프로젝트 이름을 입력하세요 |
291 | 303 |
site.userList.search = 찾으려는 사용자의 ID를 입력하세요 |
292 | 304 |
site.userList.isActive = 활성 상태 |
... | ... | @@ -295,8 +307,12 @@ |
295 | 307 |
site.user.deleteConfirm = 정말로 해당 유저를 사이트에서 탈퇴시키겠습니까? |
296 | 308 |
site.project.delete = 프로젝트 삭제 |
297 | 309 |
site.project.deleteConfirm = 정말로 해당 프로젝트를 사이트에서 삭제하겠습니까? |
298 |
-site.userlist.unlocked = 활성화된 유저 목록 |
|
299 |
-site.userlist.locked = 계정이 잠긴 유저 목록 |
|
310 |
+site.userlist.unlocked = 활성화된 유저 목록 |
|
311 |
+site.userlist.locked = 계정이 잠긴 유저 목록 |
|
312 |
+site.massMail.toAll = 모두에게 |
|
313 |
+site.massMail.toProjects = 특정 프로젝트의 멤버들에게 |
|
314 |
+site.massMail.loading = 불러오는중... |
|
315 |
+ |
|
300 | 316 |
|
301 | 317 |
#User |
302 | 318 |
user.loginId = 아이디 |
... | ... | @@ -320,9 +336,9 @@ |
320 | 336 |
user.signupBtn = 참여하기 |
321 | 337 |
user.loginWithNewPassword = 새로 설정한 비밀번호로 로그인 하세요 |
322 | 338 |
user.notExists.name = 존재하지 않는 유저입니다. |
323 |
-user.locked = 잠긴 사용자 계정입니다. |
|
324 |
-user.isLocked = 잠김여부 |
|
325 |
-user.signup.requested = 하이브 가입이 요청되었습니다. 사이트 관리자가 검토/승인 후 사용가능합니다. 감사합니다. |
|
339 |
+user.locked = 잠긴 사용자 계정입니다. |
|
340 |
+user.isLocked = 잠김여부 |
|
341 |
+user.signup.requested = 하이브 가입이 요청되었습니다. 사이트 관리자가 검토/승인 후 사용가능합니다. 감사합니다. |
|
326 | 342 |
|
327 | 343 |
#Role |
328 | 344 |
role.manager = 관리자 |
... | ... | @@ -390,6 +406,7 @@ |
390 | 406 |
mail.subject = 제목 |
391 | 407 |
mail.body = 본문 |
392 | 408 |
mail.send = 발송 |
409 |
+mail.write = 메일 쓰기 |
|
393 | 410 |
mail.fail = 메일 발송에 실패했습니다. |
394 | 411 |
mail.sended = 메일을 발송하였습니다. |
395 | 412 |
|
... | ... | @@ -399,7 +416,7 @@ |
399 | 416 |
none = 없음 |
400 | 417 |
comment = 댓글 |
401 | 418 |
noAssignee = 담당자 없음 |
402 |
-noMilestone = 마일스톤 없음 |
|
419 |
+noMilestone = 마일스톤 없음 |
|
403 | 420 |
|
404 | 421 |
#validation |
405 | 422 |
validation.tooShortPassword = 비밀번호를 4자 이상으로 만들어 주세요! |
--- conf/routes
+++ conf/routes
... | ... | @@ -35,6 +35,8 @@ |
35 | 35 |
GET /sites/setting controllers.SiteApp.setting() |
36 | 36 |
GET /sites/mail controllers.SiteApp.writeMail(errorMessage:String ?= "", sended:Boolean ?= false) |
37 | 37 |
POST /sites/mail controllers.SiteApp.sendMail() |
38 |
+GET /sites/massmail controllers.SiteApp.massMail() |
|
39 |
+POST /sites/mailList controllers.SiteApp.mailList() |
|
38 | 40 |
GET /sites/userList controllers.SiteApp.userList(pageNum: Int ?= 0, loginId: String ?= null) |
39 | 41 |
POST /sites/userList controllers.SiteApp.searchUser() |
40 | 42 |
GET /sites/user/delete controllers.SiteApp.deleteUser(userId: Long) |
... | ... | @@ -46,6 +48,9 @@ |
46 | 48 |
POST /lostPassword controllers.PasswordResetApp.requestResetPasswordEmail() |
47 | 49 |
GET /resetPassword controllers.PasswordResetApp.resetPasswordForm(s:String) |
48 | 50 |
POST /resetPassword controllers.PasswordResetApp.resetPassword() |
51 |
+GET /sites/postList controllers.SiteApp.postList(pageNum: Int ?= 1) |
|
52 |
+GET /sites/issueList controllers.SiteApp.issueList(pageNum: Int ?= 1) |
|
53 |
+ |
|
49 | 54 |
|
50 | 55 |
# Attachments |
51 | 56 |
GET /files controllers.AttachmentApp.getFileList() |
--- public/javascripts/common/hive.Common.js
+++ public/javascripts/common/hive.Common.js
... | ... | @@ -69,7 +69,7 @@ |
69 | 69 |
// 그렇지 않으면 스크립트 파일 불러온 뒤 초기화 시도 |
70 | 70 |
if(registerModule(sName, htOptions) === false){ |
71 | 71 |
htVar.htTryLoad = htVar.htTryLoad || {}; |
72 |
- htVar.htTryLoad[sName] = (typeof htVar.htTryLoad[sName] == "undefined") ? 1 : (htVar.htTryLoad[sName]++); |
|
72 |
+ htVar.htTryLoad[sName] = (typeof htVar.htTryLoad[sName] == "undefined") ? 1 : (++htVar.htTryLoad[sName]); |
|
73 | 73 |
|
74 | 74 |
if(htVar.htTryLoad[sName] > 3){ |
75 | 75 |
console.log("[HIVE] fail to load module " + sName); |
... | ... | @@ -226,7 +226,7 @@ |
226 | 226 |
welForm.ajaxForm({ |
227 | 227 |
"success" : htOptions.fOnLoad || function(){}, |
228 | 228 |
"error" : htOptions.fOnError || function(){}, |
229 |
- "datatype": htOptions.sDataType || null |
|
229 |
+ "dataType": htOptions.sDataType || null |
|
230 | 230 |
}); |
231 | 231 |
welForm.submit(); |
232 | 232 |
|
... | ... | @@ -286,4 +286,4 @@ |
286 | 286 |
|
287 | 287 |
$hive.loadModule(sModuleName, htOptions); |
288 | 288 |
} |
289 |
-};(파일 끝에 줄바꿈 문자 없음) |
|
289 |
+}; |
--- public/javascripts/common/hive.FileUploader.js
+++ public/javascripts/common/hive.FileUploader.js
... | ... | @@ -22,9 +22,13 @@ |
22 | 22 |
function _init(htOptions){ |
23 | 23 |
htOptions = htOptions || {}; |
24 | 24 |
|
25 |
- _initVar(htOptions); |
|
26 | 25 |
_initElement(htOptions); |
26 |
+ _initVar(htOptions); |
|
27 | 27 |
_attachEvent(); |
28 |
+ |
|
29 |
+ if(htVar.sMode == "edit") { |
|
30 |
+ _requestList(); |
|
31 |
+ } |
|
28 | 32 |
} |
29 | 33 |
|
30 | 34 |
/** |
... | ... | @@ -42,6 +46,10 @@ |
42 | 46 |
"beforeSubmit" : _onBeforeSubmitForm, |
43 | 47 |
"uploadProgress": _onUploadProgressForm |
44 | 48 |
}; |
49 |
+ |
|
50 |
+ htVar.sMode = htOptions.sMode; |
|
51 |
+ htVar.sResourceId = htElements.welTarget.attr('resourceId'); |
|
52 |
+ htVar.sResourceType = htElements.welTarget.attr('resourceType'); |
|
45 | 53 |
} |
46 | 54 |
|
47 | 55 |
/** |
... | ... | @@ -66,6 +74,60 @@ |
66 | 74 |
htElements.welInputFile.click(function(){ |
67 | 75 |
_setProgressBar(0); |
68 | 76 |
}); |
77 |
+ } |
|
78 |
+ |
|
79 |
+ /** |
|
80 |
+ * request attached file list |
|
81 |
+ */ |
|
82 |
+ function _requestList(){ |
|
83 |
+ var htData = _getRequestData(); |
|
84 |
+ |
|
85 |
+ $hive.sendForm({ |
|
86 |
+ "sURL" : htVar.sAction, |
|
87 |
+ "htData" : htData, |
|
88 |
+ "htOptForm": {"method":"get"}, |
|
89 |
+ "fOnLoad" : _onLoadRequest |
|
90 |
+ }); |
|
91 |
+ } |
|
92 |
+ |
|
93 |
+ /** |
|
94 |
+ * get request parameters |
|
95 |
+ * @return {Hash Table} |
|
96 |
+ */ |
|
97 |
+ function _getRequestData(){ |
|
98 |
+ var htData = {}; |
|
99 |
+ |
|
100 |
+ if(typeof htVar.sResourceType !== "undefined"){ |
|
101 |
+ htData.containerType = htVar.sResourceType; |
|
102 |
+ } |
|
103 |
+ |
|
104 |
+ if(typeof htVar.sResourceId !== "undefined"){ |
|
105 |
+ htData.containerId = htVar.sResourceId; |
|
106 |
+ } |
|
107 |
+ |
|
108 |
+ return htData; |
|
109 |
+ } |
|
110 |
+ |
|
111 |
+ function _onLoadRequest(oRes) { |
|
112 |
+ |
|
113 |
+ var aItems = []; |
|
114 |
+ var aFiles = oRes.attachments; |
|
115 |
+ |
|
116 |
+ if(aFiles == null || aFiles.length === 0){ |
|
117 |
+ return; |
|
118 |
+ } |
|
119 |
+ |
|
120 |
+ var totalFileSize = 0; |
|
121 |
+ aFiles.forEach(function(oFile) { |
|
122 |
+ var welItem = _createFileItem(oFile); |
|
123 |
+ welItem.click(_onClickListItem); |
|
124 |
+ htElements.welFileList.append(welItem); |
|
125 |
+ totalFileSize = totalFileSize + parseInt(oFile.size); |
|
126 |
+ }); |
|
127 |
+ |
|
128 |
+ _setProgressBar(100); |
|
129 |
+ _updateTotalFilesize(totalFileSize); |
|
130 |
+ |
|
69 | 131 |
} |
70 | 132 |
|
71 | 133 |
/** |
... | ... | @@ -132,7 +194,7 @@ |
132 | 194 |
|
133 | 195 |
// create list item |
134 | 196 |
var welItem = _createFileItem(oRes); |
135 |
- welItem.click(_onClickListItem); |
|
197 |
+ welItem.click(_onClickListItem); |
|
136 | 198 |
htElements.welFileList.append(welItem); |
137 | 199 |
|
138 | 200 |
_setProgressBar(100); |
--- public/javascripts/common/hive.ui.Typeahead.js
+++ public/javascripts/common/hive.ui.Typeahead.js
... | ... | @@ -60,6 +60,7 @@ |
60 | 60 |
"sURL" : htVar.sActionURL, |
61 | 61 |
"htOptForm" : {"method":"get"}, |
62 | 62 |
"htData" : {"query": sQuery}, |
63 |
+ "sDataType" : "json", |
|
63 | 64 |
"fOnLoad" : function(oData, oStatus, oXHR){ |
64 | 65 |
var sContentRange = oXHR.getResponseHeader('Content-Range'); |
65 | 66 |
|
... | ... | @@ -78,7 +79,7 @@ |
78 | 79 |
* Return whether the given content range is an entire range for items. |
79 | 80 |
* e.g) "items 10/10" |
80 | 81 |
* |
81 |
- * @param {String} sContentRange the vaule of Content-Range header from response |
|
82 |
+ * @param {String} sContentRange the value of Content-Range header from response |
|
82 | 83 |
* @return {Boolean} |
83 | 84 |
*/ |
84 | 85 |
function _isEntireRange(sContentRange){ |
... | ... | @@ -89,4 +90,4 @@ |
89 | 90 |
_init(sQuery, htOptions || {}); |
90 | 91 |
}; |
91 | 92 |
|
92 |
-})("hive.ui.Typeahead");(파일 끝에 줄바꿈 문자 없음) |
|
93 |
+})("hive.ui.Typeahead"); |
--- public/javascripts/lib/bootstrap.js
+++ public/javascripts/lib/bootstrap.js
... | ... | @@ -1,5 +1,5 @@ |
1 | 1 |
/* =================================================== |
2 |
- * bootstrap-transition.js v2.2.2 |
|
2 |
+ * bootstrap-transition.js v2.3.1 |
|
3 | 3 |
* http://twitter.github.com/bootstrap/javascript.html#transitions |
4 | 4 |
* =================================================== |
5 | 5 |
* Copyright 2012 Twitter, Inc. |
... | ... | @@ -58,7 +58,7 @@ |
58 | 58 |
}) |
59 | 59 |
|
60 | 60 |
}(window.jQuery);/* ========================================================== |
61 |
- * bootstrap-alert.js v2.2.2 |
|
61 |
+ * bootstrap-alert.js v2.3.1 |
|
62 | 62 |
* http://twitter.github.com/bootstrap/javascript.html#alerts |
63 | 63 |
* ========================================================== |
64 | 64 |
* Copyright 2012 Twitter, Inc. |
... | ... | @@ -156,7 +156,7 @@ |
156 | 156 |
$(document).on('click.alert.data-api', dismiss, Alert.prototype.close) |
157 | 157 |
|
158 | 158 |
}(window.jQuery);/* ============================================================ |
159 |
- * bootstrap-button.js v2.2.2 |
|
159 |
+ * bootstrap-button.js v2.3.1 |
|
160 | 160 |
* http://twitter.github.com/bootstrap/javascript.html#buttons |
161 | 161 |
* ============================================================ |
162 | 162 |
* Copyright 2012 Twitter, Inc. |
... | ... | @@ -260,7 +260,7 @@ |
260 | 260 |
}) |
261 | 261 |
|
262 | 262 |
}(window.jQuery);/* ========================================================== |
263 |
- * bootstrap-carousel.js v2.2.2 |
|
263 |
+ * bootstrap-carousel.js v2.3.1 |
|
264 | 264 |
* http://twitter.github.com/bootstrap/javascript.html#carousel |
265 | 265 |
* ========================================================== |
266 | 266 |
* Copyright 2012 Twitter, Inc. |
... | ... | @@ -289,6 +289,7 @@ |
289 | 289 |
|
290 | 290 |
var Carousel = function (element, options) { |
291 | 291 |
this.$element = $(element) |
292 |
+ this.$indicators = this.$element.find('.carousel-indicators') |
|
292 | 293 |
this.options = options |
293 | 294 |
this.options.pause == 'hover' && this.$element |
294 | 295 |
.on('mouseenter', $.proxy(this.pause, this)) |
... | ... | @@ -299,19 +300,24 @@ |
299 | 300 |
|
300 | 301 |
cycle: function (e) { |
301 | 302 |
if (!e) this.paused = false |
303 |
+ if (this.interval) clearInterval(this.interval); |
|
302 | 304 |
this.options.interval |
303 | 305 |
&& !this.paused |
304 | 306 |
&& (this.interval = setInterval($.proxy(this.next, this), this.options.interval)) |
305 | 307 |
return this |
306 | 308 |
} |
307 | 309 |
|
310 |
+ , getActiveIndex: function () { |
|
311 |
+ this.$active = this.$element.find('.item.active') |
|
312 |
+ this.$items = this.$active.parent().children() |
|
313 |
+ return this.$items.index(this.$active) |
|
314 |
+ } |
|
315 |
+ |
|
308 | 316 |
, to: function (pos) { |
309 |
- var $active = this.$element.find('.item.active') |
|
310 |
- , children = $active.parent().children() |
|
311 |
- , activePos = children.index($active) |
|
317 |
+ var activeIndex = this.getActiveIndex() |
|
312 | 318 |
, that = this |
313 | 319 |
|
314 |
- if (pos > (children.length - 1) || pos < 0) return |
|
320 |
+ if (pos > (this.$items.length - 1) || pos < 0) return |
|
315 | 321 |
|
316 | 322 |
if (this.sliding) { |
317 | 323 |
return this.$element.one('slid', function () { |
... | ... | @@ -319,18 +325,18 @@ |
319 | 325 |
}) |
320 | 326 |
} |
321 | 327 |
|
322 |
- if (activePos == pos) { |
|
328 |
+ if (activeIndex == pos) { |
|
323 | 329 |
return this.pause().cycle() |
324 | 330 |
} |
325 | 331 |
|
326 |
- return this.slide(pos > activePos ? 'next' : 'prev', $(children[pos])) |
|
332 |
+ return this.slide(pos > activeIndex ? 'next' : 'prev', $(this.$items[pos])) |
|
327 | 333 |
} |
328 | 334 |
|
329 | 335 |
, pause: function (e) { |
330 | 336 |
if (!e) this.paused = true |
331 | 337 |
if (this.$element.find('.next, .prev').length && $.support.transition.end) { |
332 | 338 |
this.$element.trigger($.support.transition.end) |
333 |
- this.cycle() |
|
339 |
+ this.cycle(true) |
|
334 | 340 |
} |
335 | 341 |
clearInterval(this.interval) |
336 | 342 |
this.interval = null |
... | ... | @@ -364,9 +370,18 @@ |
364 | 370 |
|
365 | 371 |
e = $.Event('slide', { |
366 | 372 |
relatedTarget: $next[0] |
373 |
+ , direction: direction |
|
367 | 374 |
}) |
368 | 375 |
|
369 | 376 |
if ($next.hasClass('active')) return |
377 |
+ |
|
378 |
+ if (this.$indicators.length) { |
|
379 |
+ this.$indicators.find('.active').removeClass('active') |
|
380 |
+ this.$element.one('slid', function () { |
|
381 |
+ var $nextIndicator = $(that.$indicators.children()[that.getActiveIndex()]) |
|
382 |
+ $nextIndicator && $nextIndicator.addClass('active') |
|
383 |
+ }) |
|
384 |
+ } |
|
370 | 385 |
|
371 | 386 |
if ($.support.transition && this.$element.hasClass('slide')) { |
372 | 387 |
this.$element.trigger(e) |
... | ... | @@ -412,7 +427,7 @@ |
412 | 427 |
if (!data) $this.data('carousel', (data = new Carousel(this, options))) |
413 | 428 |
if (typeof option == 'number') data.to(option) |
414 | 429 |
else if (action) data[action]() |
415 |
- else if (options.interval) data.cycle() |
|
430 |
+ else if (options.interval) data.pause().cycle() |
|
416 | 431 |
}) |
417 | 432 |
} |
418 | 433 |
|
... | ... | @@ -435,16 +450,23 @@ |
435 | 450 |
/* CAROUSEL DATA-API |
436 | 451 |
* ================= */ |
437 | 452 |
|
438 |
- $(document).on('click.carousel.data-api', '[data-slide]', function (e) { |
|
453 |
+ $(document).on('click.carousel.data-api', '[data-slide], [data-slide-to]', function (e) { |
|
439 | 454 |
var $this = $(this), href |
440 | 455 |
, $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7 |
441 | 456 |
, options = $.extend({}, $target.data(), $this.data()) |
457 |
+ , slideIndex |
|
458 |
+ |
|
442 | 459 |
$target.carousel(options) |
460 |
+ |
|
461 |
+ if (slideIndex = $this.attr('data-slide-to')) { |
|
462 |
+ $target.data('carousel').pause().to(slideIndex).cycle() |
|
463 |
+ } |
|
464 |
+ |
|
443 | 465 |
e.preventDefault() |
444 | 466 |
}) |
445 | 467 |
|
446 | 468 |
}(window.jQuery);/* ============================================================= |
447 |
- * bootstrap-collapse.js v2.2.2 |
|
469 |
+ * bootstrap-collapse.js v2.3.1 |
|
448 | 470 |
* http://twitter.github.com/bootstrap/javascript.html#collapse |
449 | 471 |
* ============================================================= |
450 | 472 |
* Copyright 2012 Twitter, Inc. |
... | ... | @@ -497,7 +519,7 @@ |
497 | 519 |
, actives |
498 | 520 |
, hasData |
499 | 521 |
|
500 |
- if (this.transitioning) return |
|
522 |
+ if (this.transitioning || this.$element.hasClass('in')) return |
|
501 | 523 |
|
502 | 524 |
dimension = this.dimension() |
503 | 525 |
scroll = $.camelCase(['scroll', dimension].join('-')) |
... | ... | @@ -517,7 +539,7 @@ |
517 | 539 |
|
518 | 540 |
, hide: function () { |
519 | 541 |
var dimension |
520 |
- if (this.transitioning) return |
|
542 |
+ if (this.transitioning || !this.$element.hasClass('in')) return |
|
521 | 543 |
dimension = this.dimension() |
522 | 544 |
this.reset(this.$element[dimension]()) |
523 | 545 |
this.transition('removeClass', $.Event('hide'), 'hidden') |
... | ... | @@ -574,7 +596,7 @@ |
574 | 596 |
return this.each(function () { |
575 | 597 |
var $this = $(this) |
576 | 598 |
, data = $this.data('collapse') |
577 |
- , options = typeof option == 'object' && option |
|
599 |
+ , options = $.extend({}, $.fn.collapse.defaults, $this.data(), typeof option == 'object' && option) |
|
578 | 600 |
if (!data) $this.data('collapse', (data = new Collapse(this, options))) |
579 | 601 |
if (typeof option == 'string') data[option]() |
580 | 602 |
}) |
... | ... | @@ -610,7 +632,7 @@ |
610 | 632 |
}) |
611 | 633 |
|
612 | 634 |
}(window.jQuery);/* ============================================================ |
613 |
- * bootstrap-dropdown.js v2.2.2 |
|
635 |
+ * bootstrap-dropdown.js v2.3.1 |
|
614 | 636 |
* http://twitter.github.com/bootstrap/javascript.html#dropdowns |
615 | 637 |
* ============================================================ |
616 | 638 |
* Copyright 2012 Twitter, Inc. |
... | ... | @@ -692,7 +714,10 @@ |
692 | 714 |
|
693 | 715 |
isActive = $parent.hasClass('open') |
694 | 716 |
|
695 |
- if (!isActive || (isActive && e.keyCode == 27)) return $this.click() |
|
717 |
+ if (!isActive || (isActive && e.keyCode == 27)) { |
|
718 |
+ if (e.which == 27) $parent.find(toggle).focus() |
|
719 |
+ return $this.click() |
|
720 |
+ } |
|
696 | 721 |
|
697 | 722 |
$items = $('[role=menu] li:not(.divider):visible a', $parent) |
698 | 723 |
|
... | ... | @@ -726,8 +751,9 @@ |
726 | 751 |
selector = selector && /#/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7 |
727 | 752 |
} |
728 | 753 |
|
729 |
- $parent = $(selector) |
|
730 |
- $parent.length || ($parent = $this.parent()) |
|
754 |
+ $parent = selector && $(selector) |
|
755 |
+ |
|
756 |
+ if (!$parent || !$parent.length) $parent = $this.parent() |
|
731 | 757 |
|
732 | 758 |
return $parent |
733 | 759 |
} |
... | ... | @@ -763,14 +789,15 @@ |
763 | 789 |
* =================================== */ |
764 | 790 |
|
765 | 791 |
$(document) |
766 |
- .on('click.dropdown.data-api touchstart.dropdown.data-api', clearMenus) |
|
767 |
- .on('click.dropdown touchstart.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() }) |
|
768 |
- .on('touchstart.dropdown.data-api', '.dropdown-menu', function (e) { e.stopPropagation() }) |
|
769 |
- .on('click.dropdown.data-api touchstart.dropdown.data-api' , toggle, Dropdown.prototype.toggle) |
|
770 |
- .on('keydown.dropdown.data-api touchstart.dropdown.data-api', toggle + ', [role=menu]' , Dropdown.prototype.keydown) |
|
792 |
+ .on('click.dropdown.data-api', clearMenus) |
|
793 |
+ .on('click.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() }) |
|
794 |
+ .on('click.dropdown-menu', function (e) { e.stopPropagation() }) |
|
795 |
+ .on('click.dropdown.data-api' , toggle, Dropdown.prototype.toggle) |
|
796 |
+ .on('keydown.dropdown.data-api', toggle + ', [role=menu]' , Dropdown.prototype.keydown) |
|
771 | 797 |
|
772 |
-}(window.jQuery);/* ========================================================= |
|
773 |
- * bootstrap-modal.js v2.2.2 |
|
798 |
+}(window.jQuery); |
|
799 |
+/* ========================================================= |
|
800 |
+ * bootstrap-modal.js v2.3.1 |
|
774 | 801 |
* http://twitter.github.com/bootstrap/javascript.html#modals |
775 | 802 |
* ========================================================= |
776 | 803 |
* Copyright 2012 Twitter, Inc. |
... | ... | @@ -831,8 +858,7 @@ |
831 | 858 |
that.$element.appendTo(document.body) //don't move modals dom position |
832 | 859 |
} |
833 | 860 |
|
834 |
- that.$element |
|
835 |
- .show() |
|
861 |
+ that.$element.show() |
|
836 | 862 |
|
837 | 863 |
if (transition) { |
838 | 864 |
that.$element[0].offsetWidth // force reflow |
... | ... | @@ -910,16 +936,17 @@ |
910 | 936 |
}) |
911 | 937 |
} |
912 | 938 |
|
913 |
- , hideModal: function (that) { |
|
914 |
- this.$element |
|
915 |
- .hide() |
|
916 |
- .trigger('hidden') |
|
917 |
- |
|
918 |
- this.backdrop() |
|
939 |
+ , hideModal: function () { |
|
940 |
+ var that = this |
|
941 |
+ this.$element.hide() |
|
942 |
+ this.backdrop(function () { |
|
943 |
+ that.removeBackdrop() |
|
944 |
+ that.$element.trigger('hidden') |
|
945 |
+ }) |
|
919 | 946 |
} |
920 | 947 |
|
921 | 948 |
, removeBackdrop: function () { |
922 |
- this.$backdrop.remove() |
|
949 |
+ this.$backdrop && this.$backdrop.remove() |
|
923 | 950 |
this.$backdrop = null |
924 | 951 |
} |
925 | 952 |
|
... | ... | @@ -943,6 +970,8 @@ |
943 | 970 |
|
944 | 971 |
this.$backdrop.addClass('in') |
945 | 972 |
|
973 |
+ if (!callback) return |
|
974 |
+ |
|
946 | 975 |
doAnimate ? |
947 | 976 |
this.$backdrop.one($.support.transition.end, callback) : |
948 | 977 |
callback() |
... | ... | @@ -951,8 +980,8 @@ |
951 | 980 |
this.$backdrop.removeClass('in') |
952 | 981 |
|
953 | 982 |
$.support.transition && this.$element.hasClass('fade')? |
954 |
- this.$backdrop.one($.support.transition.end, $.proxy(this.removeBackdrop, this)) : |
|
955 |
- this.removeBackdrop() |
|
983 |
+ this.$backdrop.one($.support.transition.end, callback) : |
|
984 |
+ callback() |
|
956 | 985 |
|
957 | 986 |
} else if (callback) { |
958 | 987 |
callback() |
... | ... | @@ -1015,7 +1044,7 @@ |
1015 | 1044 |
|
1016 | 1045 |
}(window.jQuery); |
1017 | 1046 |
/* =========================================================== |
1018 |
- * bootstrap-tooltip.js v2.2.2 |
|
1047 |
+ * bootstrap-tooltip.js v2.3.1 |
|
1019 | 1048 |
* http://twitter.github.com/bootstrap/javascript.html#tooltips |
1020 | 1049 |
* Inspired by the original jQuery.tipsy by Jason Frame |
1021 | 1050 |
* =========================================================== |
... | ... | @@ -1054,19 +1083,27 @@ |
1054 | 1083 |
, init: function (type, element, options) { |
1055 | 1084 |
var eventIn |
1056 | 1085 |
, eventOut |
1086 |
+ , triggers |
|
1087 |
+ , trigger |
|
1088 |
+ , i |
|
1057 | 1089 |
|
1058 | 1090 |
this.type = type |
1059 | 1091 |
this.$element = $(element) |
1060 | 1092 |
this.options = this.getOptions(options) |
1061 | 1093 |
this.enabled = true |
1062 | 1094 |
|
1063 |
- if (this.options.trigger == 'click') { |
|
1064 |
- this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this)) |
|
1065 |
- } else if (this.options.trigger != 'manual') { |
|
1066 |
- eventIn = this.options.trigger == 'hover' ? 'mouseenter' : 'focus' |
|
1067 |
- eventOut = this.options.trigger == 'hover' ? 'mouseleave' : 'blur' |
|
1068 |
- this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this)) |
|
1069 |
- this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this)) |
|
1095 |
+ triggers = this.options.trigger.split(' ') |
|
1096 |
+ |
|
1097 |
+ for (i = triggers.length; i--;) { |
|
1098 |
+ trigger = triggers[i] |
|
1099 |
+ if (trigger == 'click') { |
|
1100 |
+ this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this)) |
|
1101 |
+ } else if (trigger != 'manual') { |
|
1102 |
+ eventIn = trigger == 'hover' ? 'mouseenter' : 'focus' |
|
1103 |
+ eventOut = trigger == 'hover' ? 'mouseleave' : 'blur' |
|
1104 |
+ this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this)) |
|
1105 |
+ this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this)) |
|
1106 |
+ } |
|
1070 | 1107 |
} |
1071 | 1108 |
|
1072 | 1109 |
this.options.selector ? |
... | ... | @@ -1075,7 +1112,7 @@ |
1075 | 1112 |
} |
1076 | 1113 |
|
1077 | 1114 |
, getOptions: function (options) { |
1078 |
- options = $.extend({}, $.fn[this.type].defaults, options, this.$element.data()) |
|
1115 |
+ options = $.extend({}, $.fn[this.type].defaults, this.$element.data(), options) |
|
1079 | 1116 |
|
1080 | 1117 |
if (options.delay && typeof options.delay == 'number') { |
1081 | 1118 |
options.delay = { |
... | ... | @@ -1088,7 +1125,15 @@ |
1088 | 1125 |
} |
1089 | 1126 |
|
1090 | 1127 |
, enter: function (e) { |
1091 |
- var self = $(e.currentTarget)[this.type](this._options).data(this.type) |
|
1128 |
+ var defaults = $.fn[this.type].defaults |
|
1129 |
+ , options = {} |
|
1130 |
+ , self |
|
1131 |
+ |
|
1132 |
+ this._options && $.each(this._options, function (key, value) { |
|
1133 |
+ if (defaults[key] != value) options[key] = value |
|
1134 |
+ }, this) |
|
1135 |
+ |
|
1136 |
+ self = $(e.currentTarget)[this.type](options).data(this.type) |
|
1092 | 1137 |
|
1093 | 1138 |
if (!self.options.delay || !self.options.delay.show) return self.show() |
1094 | 1139 |
|
... | ... | @@ -1113,14 +1158,16 @@ |
1113 | 1158 |
|
1114 | 1159 |
, show: function () { |
1115 | 1160 |
var $tip |
1116 |
- , inside |
|
1117 | 1161 |
, pos |
1118 | 1162 |
, actualWidth |
1119 | 1163 |
, actualHeight |
1120 | 1164 |
, placement |
1121 | 1165 |
, tp |
1166 |
+ , e = $.Event('show') |
|
1122 | 1167 |
|
1123 | 1168 |
if (this.hasContent() && this.enabled) { |
1169 |
+ this.$element.trigger(e) |
|
1170 |
+ if (e.isDefaultPrevented()) return |
|
1124 | 1171 |
$tip = this.tip() |
1125 | 1172 |
this.setContent() |
1126 | 1173 |
|
... | ... | @@ -1132,19 +1179,18 @@ |
1132 | 1179 |
this.options.placement.call(this, $tip[0], this.$element[0]) : |
1133 | 1180 |
this.options.placement |
1134 | 1181 |
|
1135 |
- inside = /in/.test(placement) |
|
1136 |
- |
|
1137 | 1182 |
$tip |
1138 | 1183 |
.detach() |
1139 | 1184 |
.css({ top: 0, left: 0, display: 'block' }) |
1140 |
- .insertAfter(this.$element) |
|
1141 | 1185 |
|
1142 |
- pos = this.getPosition(inside) |
|
1186 |
+ this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element) |
|
1187 |
+ |
|
1188 |
+ pos = this.getPosition() |
|
1143 | 1189 |
|
1144 | 1190 |
actualWidth = $tip[0].offsetWidth |
1145 | 1191 |
actualHeight = $tip[0].offsetHeight |
1146 | 1192 |
|
1147 |
- switch (inside ? placement.split(' ')[1] : placement) { |
|
1193 |
+ switch (placement) { |
|
1148 | 1194 |
case 'bottom': |
1149 | 1195 |
tp = {top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2} |
1150 | 1196 |
break |
... | ... | @@ -1159,11 +1205,56 @@ |
1159 | 1205 |
break |
1160 | 1206 |
} |
1161 | 1207 |
|
1162 |
- $tip |
|
1163 |
- .offset(tp) |
|
1164 |
- .addClass(placement) |
|
1165 |
- .addClass('in') |
|
1208 |
+ this.applyPlacement(tp, placement) |
|
1209 |
+ this.$element.trigger('shown') |
|
1166 | 1210 |
} |
1211 |
+ } |
|
1212 |
+ |
|
1213 |
+ , applyPlacement: function(offset, placement){ |
|
1214 |
+ var $tip = this.tip() |
|
1215 |
+ , width = $tip[0].offsetWidth |
|
1216 |
+ , height = $tip[0].offsetHeight |
|
1217 |
+ , actualWidth |
|
1218 |
+ , actualHeight |
|
1219 |
+ , delta |
|
1220 |
+ , replace |
|
1221 |
+ |
|
1222 |
+ $tip |
|
1223 |
+ .offset(offset) |
|
1224 |
+ .addClass(placement) |
|
1225 |
+ .addClass('in') |
|
1226 |
+ |
|
1227 |
+ actualWidth = $tip[0].offsetWidth |
|
1228 |
+ actualHeight = $tip[0].offsetHeight |
|
1229 |
+ |
|
1230 |
+ if (placement == 'top' && actualHeight != height) { |
|
1231 |
+ offset.top = offset.top + height - actualHeight |
|
1232 |
+ replace = true |
|
1233 |
+ } |
|
1234 |
+ |
|
1235 |
+ if (placement == 'bottom' || placement == 'top') { |
|
1236 |
+ delta = 0 |
|
1237 |
+ |
|
1238 |
+ if (offset.left < 0){ |
|
1239 |
+ delta = offset.left * -2 |
|
1240 |
+ offset.left = 0 |
|
1241 |
+ $tip.offset(offset) |
|
1242 |
+ actualWidth = $tip[0].offsetWidth |
|
1243 |
+ actualHeight = $tip[0].offsetHeight |
|
1244 |
+ } |
|
1245 |
+ |
|
1246 |
+ this.replaceArrow(delta - width + actualWidth, actualWidth, 'left') |
|
1247 |
+ } else { |
|
1248 |
+ this.replaceArrow(actualHeight - height, actualHeight, 'top') |
|
1249 |
+ } |
|
1250 |
+ |
|
1251 |
+ if (replace) $tip.offset(offset) |
|
1252 |
+ } |
|
1253 |
+ |
|
1254 |
+ , replaceArrow: function(delta, dimension, position){ |
|
1255 |
+ this |
|
1256 |
+ .arrow() |
|
1257 |
+ .css(position, delta ? (50 * (1 - delta / dimension) + "%") : '') |
|
1167 | 1258 |
} |
1168 | 1259 |
|
1169 | 1260 |
, setContent: function () { |
... | ... | @@ -1177,6 +1268,10 @@ |
1177 | 1268 |
, hide: function () { |
1178 | 1269 |
var that = this |
1179 | 1270 |
, $tip = this.tip() |
1271 |
+ , e = $.Event('hide') |
|
1272 |
+ |
|
1273 |
+ this.$element.trigger(e) |
|
1274 |
+ if (e.isDefaultPrevented()) return |
|
1180 | 1275 |
|
1181 | 1276 |
$tip.removeClass('in') |
1182 | 1277 |
|
... | ... | @@ -1195,13 +1290,15 @@ |
1195 | 1290 |
removeWithAnimation() : |
1196 | 1291 |
$tip.detach() |
1197 | 1292 |
|
1293 |
+ this.$element.trigger('hidden') |
|
1294 |
+ |
|
1198 | 1295 |
return this |
1199 | 1296 |
} |
1200 | 1297 |
|
1201 | 1298 |
, fixTitle: function () { |
1202 | 1299 |
var $e = this.$element |
1203 | 1300 |
if ($e.attr('title') || typeof($e.attr('data-original-title')) != 'string') { |
1204 |
- $e.attr('data-original-title', $e.attr('title') || '').removeAttr('title') |
|
1301 |
+ $e.attr('data-original-title', $e.attr('title') || '').attr('title', '') |
|
1205 | 1302 |
} |
1206 | 1303 |
} |
1207 | 1304 |
|
... | ... | @@ -1209,11 +1306,12 @@ |
1209 | 1306 |
return this.getTitle() |
1210 | 1307 |
} |
1211 | 1308 |
|
1212 |
- , getPosition: function (inside) { |
|
1213 |
- return $.extend({}, (inside ? {top: 0, left: 0} : this.$element.offset()), { |
|
1214 |
- width: this.$element[0].offsetWidth |
|
1215 |
- , height: this.$element[0].offsetHeight |
|
1216 |
- }) |
|
1309 |
+ , getPosition: function () { |
|
1310 |
+ var el = this.$element[0] |
|
1311 |
+ return $.extend({}, (typeof el.getBoundingClientRect == 'function') ? el.getBoundingClientRect() : { |
|
1312 |
+ width: el.offsetWidth |
|
1313 |
+ , height: el.offsetHeight |
|
1314 |
+ }, this.$element.offset()) |
|
1217 | 1315 |
} |
1218 | 1316 |
|
1219 | 1317 |
, getTitle: function () { |
... | ... | @@ -1229,6 +1327,10 @@ |
1229 | 1327 |
|
1230 | 1328 |
, tip: function () { |
1231 | 1329 |
return this.$tip = this.$tip || $(this.options.template) |
1330 |
+ } |
|
1331 |
+ |
|
1332 |
+ , arrow: function(){ |
|
1333 |
+ return this.$arrow = this.$arrow || this.tip().find(".tooltip-arrow") |
|
1232 | 1334 |
} |
1233 | 1335 |
|
1234 | 1336 |
, validate: function () { |
... | ... | @@ -1252,8 +1354,8 @@ |
1252 | 1354 |
} |
1253 | 1355 |
|
1254 | 1356 |
, toggle: function (e) { |
1255 |
- var self = $(e.currentTarget)[this.type](this._options).data(this.type) |
|
1256 |
- self[self.tip().hasClass('in') ? 'hide' : 'show']() |
|
1357 |
+ var self = e ? $(e.currentTarget)[this.type](this._options).data(this.type) : this |
|
1358 |
+ self.tip().hasClass('in') ? self.hide() : self.show() |
|
1257 | 1359 |
} |
1258 | 1360 |
|
1259 | 1361 |
, destroy: function () { |
... | ... | @@ -1285,10 +1387,11 @@ |
1285 | 1387 |
, placement: 'top' |
1286 | 1388 |
, selector: false |
1287 | 1389 |
, template: '<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>' |
1288 |
- , trigger: 'hover' |
|
1390 |
+ , trigger: 'hover focus' |
|
1289 | 1391 |
, title: '' |
1290 | 1392 |
, delay: 0 |
1291 | 1393 |
, html: false |
1394 |
+ , container: false |
|
1292 | 1395 |
} |
1293 | 1396 |
|
1294 | 1397 |
|
... | ... | @@ -1300,8 +1403,9 @@ |
1300 | 1403 |
return this |
1301 | 1404 |
} |
1302 | 1405 |
|
1303 |
-}(window.jQuery);/* =========================================================== |
|
1304 |
- * bootstrap-popover.js v2.2.2 |
|
1406 |
+}(window.jQuery); |
|
1407 |
+/* =========================================================== |
|
1408 |
+ * bootstrap-popover.js v2.3.1 |
|
1305 | 1409 |
* http://twitter.github.com/bootstrap/javascript.html#popovers |
1306 | 1410 |
* =========================================================== |
1307 | 1411 |
* Copyright 2012 Twitter, Inc. |
... | ... | @@ -1360,8 +1464,8 @@ |
1360 | 1464 |
, $e = this.$element |
1361 | 1465 |
, o = this.options |
1362 | 1466 |
|
1363 |
- content = $e.attr('data-content') |
|
1364 |
- || (typeof o.content == 'function' ? o.content.call($e[0]) : o.content) |
|
1467 |
+ content = (typeof o.content == 'function' ? o.content.call($e[0]) : o.content) |
|
1468 |
+ || $e.attr('data-content') |
|
1365 | 1469 |
|
1366 | 1470 |
return content |
1367 | 1471 |
} |
... | ... | @@ -1401,7 +1505,7 @@ |
1401 | 1505 |
placement: 'right' |
1402 | 1506 |
, trigger: 'click' |
1403 | 1507 |
, content: '' |
1404 |
- , template: '<div class="popover"><div class="arrow"></div><div class="popover-inner"><h3 class="popover-title"></h3><div class="popover-content"></div></div></div>' |
|
1508 |
+ , template: '<div class="popover"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>' |
|
1405 | 1509 |
}) |
1406 | 1510 |
|
1407 | 1511 |
|
... | ... | @@ -1413,8 +1517,9 @@ |
1413 | 1517 |
return this |
1414 | 1518 |
} |
1415 | 1519 |
|
1416 |
-}(window.jQuery);/* ============================================================= |
|
1417 |
- * bootstrap-scrollspy.js v2.2.2 |
|
1520 |
+}(window.jQuery); |
|
1521 |
+/* ============================================================= |
|
1522 |
+ * bootstrap-scrollspy.js v2.3.1 |
|
1418 | 1523 |
* http://twitter.github.com/bootstrap/javascript.html#scrollspy |
1419 | 1524 |
* ============================================================= |
1420 | 1525 |
* Copyright 2012 Twitter, Inc. |
... | ... | @@ -1474,7 +1579,7 @@ |
1474 | 1579 |
, $href = /^#\w/.test(href) && $(href) |
1475 | 1580 |
return ( $href |
1476 | 1581 |
&& $href.length |
1477 |
- && [[ $href.position().top + self.$scrollElement.scrollTop(), href ]] ) || null |
|
1582 |
+ && [[ $href.position().top + (!$.isWindow(self.$scrollElement.get(0)) && self.$scrollElement.scrollTop()), href ]] ) || null |
|
1478 | 1583 |
}) |
1479 | 1584 |
.sort(function (a, b) { return a[0] - b[0] }) |
1480 | 1585 |
.each(function () { |
... | ... | @@ -1575,7 +1680,7 @@ |
1575 | 1680 |
}) |
1576 | 1681 |
|
1577 | 1682 |
}(window.jQuery);/* ======================================================== |
1578 |
- * bootstrap-tab.js v2.2.2 |
|
1683 |
+ * bootstrap-tab.js v2.3.1 |
|
1579 | 1684 |
* http://twitter.github.com/bootstrap/javascript.html#tabs |
1580 | 1685 |
* ======================================================== |
1581 | 1686 |
* Copyright 2012 Twitter, Inc. |
... | ... | @@ -1718,7 +1823,7 @@ |
1718 | 1823 |
}) |
1719 | 1824 |
|
1720 | 1825 |
}(window.jQuery);/* ============================================================= |
1721 |
- * bootstrap-typeahead.js v2.2.2 |
|
1826 |
+ * bootstrap-typeahead.js v2.3.1 |
|
1722 | 1827 |
* http://twitter.github.com/bootstrap/javascript.html#typeahead |
1723 | 1828 |
* ============================================================= |
1724 | 1829 |
* Copyright 2012 Twitter, Inc. |
... | ... | @@ -1891,6 +1996,7 @@ |
1891 | 1996 |
|
1892 | 1997 |
, listen: function () { |
1893 | 1998 |
this.$element |
1999 |
+ .on('focus', $.proxy(this.focus, this)) |
|
1894 | 2000 |
.on('blur', $.proxy(this.blur, this)) |
1895 | 2001 |
.on('keypress', $.proxy(this.keypress, this)) |
1896 | 2002 |
.on('keyup', $.proxy(this.keyup, this)) |
... | ... | @@ -1902,6 +2008,7 @@ |
1902 | 2008 |
this.$menu |
1903 | 2009 |
.on('click', $.proxy(this.click, this)) |
1904 | 2010 |
.on('mouseenter', 'li', $.proxy(this.mouseenter, this)) |
2011 |
+ .on('mouseleave', 'li', $.proxy(this.mouseleave, this)) |
|
1905 | 2012 |
} |
1906 | 2013 |
|
1907 | 2014 |
, eventSupported: function(eventName) { |
... | ... | @@ -1975,20 +2082,31 @@ |
1975 | 2082 |
e.preventDefault() |
1976 | 2083 |
} |
1977 | 2084 |
|
2085 |
+ , focus: function (e) { |
|
2086 |
+ this.focused = true |
|
2087 |
+ } |
|
2088 |
+ |
|
1978 | 2089 |
, blur: function (e) { |
1979 |
- var that = this |
|
1980 |
- setTimeout(function () { that.hide() }, 150) |
|
2090 |
+ this.focused = false |
|
2091 |
+ if (!this.mousedover && this.shown) this.hide() |
|
1981 | 2092 |
} |
1982 | 2093 |
|
1983 | 2094 |
, click: function (e) { |
1984 | 2095 |
e.stopPropagation() |
1985 | 2096 |
e.preventDefault() |
1986 | 2097 |
this.select() |
2098 |
+ this.$element.focus() |
|
1987 | 2099 |
} |
1988 | 2100 |
|
1989 | 2101 |
, mouseenter: function (e) { |
2102 |
+ this.mousedover = true |
|
1990 | 2103 |
this.$menu.find('.active').removeClass('active') |
1991 | 2104 |
$(e.currentTarget).addClass('active') |
2105 |
+ } |
|
2106 |
+ |
|
2107 |
+ , mouseleave: function (e) { |
|
2108 |
+ this.mousedover = false |
|
2109 |
+ if (!this.focused && this.shown) this.hide() |
|
1992 | 2110 |
} |
1993 | 2111 |
|
1994 | 2112 |
} |
... | ... | @@ -2035,13 +2153,12 @@ |
2035 | 2153 |
$(document).on('focus.typeahead.data-api', '[data-provide="typeahead"]', function (e) { |
2036 | 2154 |
var $this = $(this) |
2037 | 2155 |
if ($this.data('typeahead')) return |
2038 |
- e.preventDefault() |
|
2039 | 2156 |
$this.typeahead($this.data()) |
2040 | 2157 |
}) |
2041 | 2158 |
|
2042 | 2159 |
}(window.jQuery); |
2043 | 2160 |
/* ========================================================== |
2044 |
- * bootstrap-affix.js v2.2.2 |
|
2161 |
+ * bootstrap-affix.js v2.3.1 |
|
2045 | 2162 |
* http://twitter.github.com/bootstrap/javascript.html#affix |
2046 | 2163 |
* ========================================================== |
2047 | 2164 |
* Copyright 2012 Twitter, Inc. |
--- public/javascripts/lib/bootstrap.min.js
... | ... | @@ -1,6 +0,0 @@ |
1 | -/*! | |
2 | -* Bootstrap.js by @fat & @mdo | |
3 | -* Copyright 2012 Twitter, Inc. | |
4 | -* http://www.apache.org/licenses/LICENSE-2.0.txt | |
5 | -*/ | |
6 | -!function($){"use strict";$(function(){$.support.transition=function(){var transitionEnd=function(){var name,el=document.createElement("bootstrap"),transEndEventNames={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(name in transEndEventNames)if(void 0!==el.style[name])return transEndEventNames[name]}();return transitionEnd&&{end:transitionEnd}}()})}(window.jQuery),!function($){"use strict";var dismiss='[data-dismiss="alert"]',Alert=function(el){$(el).on("click",dismiss,this.close)};Alert.prototype.close=function(e){function removeElement(){$parent.trigger("closed").remove()}var $parent,$this=$(this),selector=$this.attr("data-target");selector||(selector=$this.attr("href"),selector=selector&&selector.replace(/.*(?=#[^\s]*$)/,"")),$parent=$(selector),e&&e.preventDefault(),$parent.length||($parent=$this.hasClass("alert")?$this:$this.parent()),$parent.trigger(e=$.Event("close")),e.isDefaultPrevented()||($parent.removeClass("in"),$.support.transition&&$parent.hasClass("fade")?$parent.on($.support.transition.end,removeElement):removeElement())};var old=$.fn.alert;$.fn.alert=function(option){return this.each(function(){var $this=$(this),data=$this.data("alert");data||$this.data("alert",data=new Alert(this)),"string"==typeof option&&data[option].call($this)})},$.fn.alert.Constructor=Alert,$.fn.alert.noConflict=function(){return $.fn.alert=old,this},$(document).on("click.alert.data-api",dismiss,Alert.prototype.close)}(window.jQuery),!function($){"use strict";var Button=function(element,options){this.$element=$(element),this.options=$.extend({},$.fn.button.defaults,options)};Button.prototype.setState=function(state){var d="disabled",$el=this.$element,data=$el.data(),val=$el.is("input")?"val":"html";state+="Text",data.resetText||$el.data("resetText",$el[val]()),$el[val](data[state]||this.options[state]),setTimeout(function(){"loadingText"==state?$el.addClass(d).attr(d,d):$el.removeClass(d).removeAttr(d)},0)},Button.prototype.toggle=function(){var $parent=this.$element.closest('[data-toggle="buttons-radio"]');$parent&&$parent.find(".active").removeClass("active"),this.$element.toggleClass("active")};var old=$.fn.button;$.fn.button=function(option){return this.each(function(){var $this=$(this),data=$this.data("button"),options="object"==typeof option&&option;data||$this.data("button",data=new Button(this,options)),"toggle"==option?data.toggle():option&&data.setState(option)})},$.fn.button.defaults={loadingText:"loading..."},$.fn.button.Constructor=Button,$.fn.button.noConflict=function(){return $.fn.button=old,this},$(document).on("click.button.data-api","[data-toggle^=button]",function(e){var $btn=$(e.target);$btn.hasClass("btn")||($btn=$btn.closest(".btn")),$btn.button("toggle")})}(window.jQuery),!function($){"use strict";var Carousel=function(element,options){this.$element=$(element),this.options=options,"hover"==this.options.pause&&this.$element.on("mouseenter",$.proxy(this.pause,this)).on("mouseleave",$.proxy(this.cycle,this))};Carousel.prototype={cycle:function(e){return e||(this.paused=!1),this.options.interval&&!this.paused&&(this.interval=setInterval($.proxy(this.next,this),this.options.interval)),this},to:function(pos){var $active=this.$element.find(".item.active"),children=$active.parent().children(),activePos=children.index($active),that=this;if(!(pos>children.length-1||0>pos))return this.sliding?this.$element.one("slid",function(){that.to(pos)}):activePos==pos?this.pause().cycle():this.slide(pos>activePos?"next":"prev",$(children[pos]))},pause:function(e){return e||(this.paused=!0),this.$element.find(".next, .prev").length&&$.support.transition.end&&(this.$element.trigger($.support.transition.end),this.cycle()),clearInterval(this.interval),this.interval=null,this},next:function(){return this.sliding?void 0:this.slide("next")},prev:function(){return this.sliding?void 0:this.slide("prev")},slide:function(type,next){var e,$active=this.$element.find(".item.active"),$next=next||$active[type](),isCycling=this.interval,direction="next"==type?"left":"right",fallback="next"==type?"first":"last",that=this;if(this.sliding=!0,isCycling&&this.pause(),$next=$next.length?$next:this.$element.find(".item")[fallback](),e=$.Event("slide",{relatedTarget:$next[0]}),!$next.hasClass("active")){if($.support.transition&&this.$element.hasClass("slide")){if(this.$element.trigger(e),e.isDefaultPrevented())return;$next.addClass(type),$next[0].offsetWidth,$active.addClass(direction),$next.addClass(direction),this.$element.one($.support.transition.end,function(){$next.removeClass([type,direction].join(" ")).addClass("active"),$active.removeClass(["active",direction].join(" ")),that.sliding=!1,setTimeout(function(){that.$element.trigger("slid")},0)})}else{if(this.$element.trigger(e),e.isDefaultPrevented())return;$active.removeClass("active"),$next.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return isCycling&&this.cycle(),this}}};var old=$.fn.carousel;$.fn.carousel=function(option){return this.each(function(){var $this=$(this),data=$this.data("carousel"),options=$.extend({},$.fn.carousel.defaults,"object"==typeof option&&option),action="string"==typeof option?option:options.slide;data||$this.data("carousel",data=new Carousel(this,options)),"number"==typeof option?data.to(option):action?data[action]():options.interval&&data.cycle()})},$.fn.carousel.defaults={interval:5e3,pause:"hover"},$.fn.carousel.Constructor=Carousel,$.fn.carousel.noConflict=function(){return $.fn.carousel=old,this},$(document).on("click.carousel.data-api","[data-slide]",function(e){var href,$this=$(this),$target=$($this.attr("data-target")||(href=$this.attr("href"))&&href.replace(/.*(?=#[^\s]+$)/,"")),options=$.extend({},$target.data(),$this.data());$target.carousel(options),e.preventDefault()})}(window.jQuery),!function($){"use strict";var Collapse=function(element,options){this.$element=$(element),this.options=$.extend({},$.fn.collapse.defaults,options),this.options.parent&&(this.$parent=$(this.options.parent)),this.options.toggle&&this.toggle()};Collapse.prototype={constructor:Collapse,dimension:function(){var hasWidth=this.$element.hasClass("width");return hasWidth?"width":"height"},show:function(){var dimension,scroll,actives,hasData;if(!this.transitioning){if(dimension=this.dimension(),scroll=$.camelCase(["scroll",dimension].join("-")),actives=this.$parent&&this.$parent.find("> .accordion-group > .in"),actives&&actives.length){if(hasData=actives.data("collapse"),hasData&&hasData.transitioning)return;actives.collapse("hide"),hasData||actives.data("collapse",null)}this.$element[dimension](0),this.transition("addClass",$.Event("show"),"shown"),$.support.transition&&this.$element[dimension](this.$element[0][scroll])}},hide:function(){var dimension;this.transitioning||(dimension=this.dimension(),this.reset(this.$element[dimension]()),this.transition("removeClass",$.Event("hide"),"hidden"),this.$element[dimension](0))},reset:function(size){var dimension=this.dimension();return this.$element.removeClass("collapse")[dimension](size||"auto")[0].offsetWidth,this.$element[null!==size?"addClass":"removeClass"]("collapse"),this},transition:function(method,startEvent,completeEvent){var that=this,complete=function(){"show"==startEvent.type&&that.reset(),that.transitioning=0,that.$element.trigger(completeEvent)};this.$element.trigger(startEvent),startEvent.isDefaultPrevented()||(this.transitioning=1,this.$element[method]("in"),$.support.transition&&this.$element.hasClass("collapse")?this.$element.one($.support.transition.end,complete):complete())},toggle:function(){this[this.$element.hasClass("in")?"hide":"show"]()}};var old=$.fn.collapse;$.fn.collapse=function(option){return this.each(function(){var $this=$(this),data=$this.data("collapse"),options="object"==typeof option&&option;data||$this.data("collapse",data=new Collapse(this,options)),"string"==typeof option&&data[ |