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

update styles. recoding fileUploader javascript
uploader.js replaced with fileUploader.js.
@65c6d9920dc50904f3a08dd939b366809b3e4678
--- app/assets/stylesheets/less/_page.less
+++ app/assets/stylesheets/less/_page.less
... | ... | @@ -238,7 +238,7 @@ |
238 | 238 |
&:hover { color:@orange; } |
239 | 239 |
} |
240 | 240 |
|
241 |
- .page { padding:40px 39px 30px 39px; } |
|
241 |
+ .page { padding:40px 39px 60px 39px; } |
|
242 | 242 |
} |
243 | 243 |
|
244 | 244 |
.page-wrap { |
... | ... | @@ -292,9 +292,8 @@ |
292 | 292 |
} |
293 | 293 |
|
294 | 294 |
.page { |
295 |
- padding: 0 20px; |
|
296 |
- padding-top:6px; |
|
297 |
- |
|
295 |
+ padding:6px 20px 60px 20px; |
|
296 |
+ |
|
298 | 297 |
/*margin-bottom: 40px;*/ |
299 | 298 |
border: 1px solid @gray-ea; |
300 | 299 |
border-top:none; |
... | ... | @@ -917,7 +916,7 @@ |
917 | 916 |
//-- new project |
918 | 917 |
.form-wrap { |
919 | 918 |
&.new-project { |
920 |
- margin: 30px 19px; |
|
919 |
+ margin: 30px 19px 0px 19px; |
|
921 | 920 |
|
922 | 921 |
.text { |
923 | 922 |
width: 866px; |
... | ... | @@ -1555,21 +1554,19 @@ |
1555 | 1554 |
width: 128px; /*147px;*/ |
1556 | 1555 |
} |
1557 | 1556 |
.btns { |
1557 |
+ text-transform: capitalize; |
|
1558 | 1558 |
margin-top: 6px; |
1559 |
+ .n-btn { |
|
1560 |
+ /*width:54px; font-size:12px; letter-spacing: -1px;*/ |
|
1561 |
+ width:68px; padding:3px; padding-top:5px; font-size:12px; |
|
1562 |
+ } |
|
1559 | 1563 |
} |
1560 | 1564 |
p { |
1561 | 1565 |
word-wrap: break-word; |
1562 | 1566 |
margin-bottom: 2px; |
1563 | 1567 |
} |
1564 |
- .uname { |
|
1565 |
- font-size: 20px; |
|
1566 |
- } |
|
1567 |
- .name { |
|
1568 |
- font-size: 11px; |
|
1569 |
- } |
|
1570 |
- .btns { |
|
1571 |
- text-transform: capitalize; |
|
1572 |
- } |
|
1568 |
+ .uname { font-size: 20px; } |
|
1569 |
+ .name { font-size: 11px; } |
|
1573 | 1570 |
} |
1574 | 1571 |
} |
1575 | 1572 |
} |
... | ... | @@ -1729,7 +1726,7 @@ |
1729 | 1726 |
width: 584px; |
1730 | 1727 |
} |
1731 | 1728 |
.textarea { |
1732 |
- height: 354px; |
|
1729 |
+ height: 305px; |
|
1733 | 1730 |
resize: vertical; |
1734 | 1731 |
} |
1735 | 1732 |
.actions { |
... | ... | @@ -2051,12 +2048,28 @@ |
2051 | 2048 |
|
2052 | 2049 |
} |
2053 | 2050 |
|
2051 |
+div.markdown-preview { |
|
2052 |
+ font-size: 12px; |
|
2053 |
+ margin:0px; margin-bottom:9px; |
|
2054 |
+ vertical-align : middle; |
|
2055 |
+ border:1px solid #ccc; |
|
2056 |
+ .border-radius(2px); |
|
2057 |
+ .inline-block; |
|
2058 |
+} |
|
2059 |
+ |
|
2054 | 2060 |
.write-comment-box { |
2055 | 2061 |
padding: 17px 20px 15px; |
2056 | 2062 |
.write-comment-wrap { |
2057 | 2063 |
font-size: 0; |
2058 | 2064 |
margin-bottom: 10px; |
2065 |
+ |
|
2066 |
+ div.markdown-preview { |
|
2067 |
+ /*width : 727px !important; |
|
2068 |
+ min-height: 88px !important;*/ |
|
2069 |
+ .border-radius(2px 0 0 2px); |
|
2070 |
+ } |
|
2059 | 2071 |
} |
2072 |
+ |
|
2060 | 2073 |
.comment-btn { |
2061 | 2074 |
vertical-align:top; |
2062 | 2075 |
background-color: @white; |
... | ... | @@ -2065,16 +2078,16 @@ |
2065 | 2078 |
font-weight: bold; |
2066 | 2079 |
border: 1px solid @gray-cc; |
2067 | 2080 |
border-left: 0 none; |
2068 |
- .border-radius(0 4px 4px 0); |
|
2081 |
+ .border-radius(0 2px 2px 0); |
|
2069 | 2082 |
width: 110px; |
2070 | 2083 |
height: 90px; |
2071 | 2084 |
} |
2072 | 2085 |
.comment { |
2073 | 2086 |
width: 715px; |
2074 | 2087 |
height: 80px; |
2075 |
- margin: 0; |
|
2088 |
+ margin: 0; margin-bottom:9px; |
|
2076 | 2089 |
resize:vertical; |
2077 |
- .border-radius(4px 0 0 4px); |
|
2090 |
+ .border-radius(2px 0 0 2px); |
|
2078 | 2091 |
.box-shadow(none); |
2079 | 2092 |
&:focus { |
2080 | 2093 |
border: 1px solid @gray-cc; |
... | ... | @@ -2097,6 +2110,7 @@ |
2097 | 2110 |
margin-left: 5px; |
2098 | 2111 |
} |
2099 | 2112 |
} |
2113 |
+.board-actrow { margin:20px 0; } |
|
2100 | 2114 |
|
2101 | 2115 |
.attach-wrap { |
2102 | 2116 |
> div { |
... | ... | @@ -2109,6 +2123,10 @@ |
2109 | 2123 |
.attach-info-wrap { |
2110 | 2124 |
width: 715px; |
2111 | 2125 |
font-size: 11px; |
2126 |
+ /*opacity:0; |
|
2127 |
+ -webkit-transition-duration:0.3s; |
|
2128 |
+ -webkit-transition-property:opacity;*/ |
|
2129 |
+ |
|
2112 | 2130 |
.sp-line { |
2113 | 2131 |
border-left: 1px solid @white; |
2114 | 2132 |
.box-shadow(inset 1px 0 0 rgba(0, 0, 0, 0.15)); |
... | ... | @@ -2145,9 +2163,14 @@ |
2145 | 2163 |
.attached-file { |
2146 | 2164 |
color: #999; |
2147 | 2165 |
font-size: 11px; |
2166 |
+ cursor: pointer; |
|
2167 |
+ -webkit-transition-property: color; |
|
2168 |
+ -webkit-transition-duration: 0.5s; |
|
2169 |
+ &:hover { color: @orange; } |
|
2148 | 2170 |
} |
2149 | 2171 |
.attached-delete { |
2150 | 2172 |
margin-left: 10px; |
2173 |
+ cursor:pointer; |
|
2151 | 2174 |
} |
2152 | 2175 |
} |
2153 | 2176 |
} |
... | ... | @@ -2181,6 +2204,7 @@ |
2181 | 2204 |
color:#959595; font-weight:bold; |
2182 | 2205 |
margin-right:5px; |
2183 | 2206 |
} |
2207 |
+ .n-alert .n-inner { font-weight:normal !important; } |
|
2184 | 2208 |
} |
2185 | 2209 |
} |
2186 | 2210 |
.content-wrap { |
... | ... | @@ -2336,7 +2360,7 @@ |
2336 | 2360 |
font-weight:bold; |
2337 | 2361 |
|
2338 | 2362 |
strong { |
2339 |
- color:#fff; .border-radius(3px); |
|
2363 |
+ color:#fff; .border-radius(2px); |
|
2340 | 2364 |
font-size: 11px; padding: 4px; |
2341 | 2365 |
text-shadow: -1px -1px rgba(0,0,0,0.3); |
2342 | 2366 |
margin-right: 5px; |
... | ... | @@ -2675,6 +2699,7 @@ |
2675 | 2699 |
|
2676 | 2700 |
.user-box { |
2677 | 2701 |
margin: 15px 0 25px; |
2702 |
+ margin-bottom:0px; |
|
2678 | 2703 |
overflow: hidden; |
2679 | 2704 |
} |
2680 | 2705 |
|
... | ... | @@ -3001,3 +3026,4 @@ |
3001 | 3026 |
hr.dark-gray { border-top:1px solid #d4d4d4; } |
3002 | 3027 |
*/ |
3003 | 3028 |
.alert { position:absolute; width:910px; top:76px; margin-left:auto; } |
3029 |
+form { margin:0 0 2px; }(No newline at end of file) |
--- app/views/board/newPost.scala.html
+++ app/views/board/newPost.scala.html
... | ... | @@ -7,23 +7,29 @@ |
7 | 7 |
<div class="page"> |
8 | 8 |
@views.html.prjmenu(project, utils.MenuType.BOARD, "main-menu-only") |
9 | 9 |
|
10 |
- <div class="content-wrap"> |
|
10 |
+ <div class="content-wrap frm-wrap"> |
|
11 | 11 |
|
12 | 12 |
@helper.form(action=routes.BoardApp.newPost(project.owner, project.name), 'enctype -> "multipart/form-data", 'class->"nm"){ |
13 |
- <div id="warning" class="n-alert hide"> |
|
14 |
- <div class="n-inner"> |
|
15 |
- <span class="msg">제목과 본문에 빈칸이 있습니다.</span> |
|
16 |
- <a href="#" class="ico btn-delete"></a> |
|
17 |
- </div> |
|
18 |
- </div> |
|
19 |
- <label for="title"> |
|
20 |
- @helper.inputText(form("title"), 'class->"text title", 'placeholder -> Messages("post.new.title"), 'maxlength -> "250", 'tabindex -> 1) |
|
21 |
- </label> |
|
22 |
- <label for="content"> |
|
23 |
- @helper.textarea(form("contents"), 'markdown -> true, 'class->"text content", 'tabindex -> 2) |
|
24 |
- </label> |
|
25 |
- @if(UserApp.currentUser() != UserApp.anonymous) { |
|
26 |
- <div id="upload" resourceType=@ResourceType.BOARD_POST></div> |
|
13 |
+ |
|
14 |
+ <dl> |
|
15 |
+ <dt> |
|
16 |
+ <div id="warning" class="n-alert hide"> |
|
17 |
+ <div class="n-inner"> |
|
18 |
+ <span class="msg">제목과 본문에 빈칸이 있습니다.</span> |
|
19 |
+ <a href="#" class="ico btn-delete"></a> |
|
20 |
+ </div> |
|
21 |
+ </div> |
|
22 |
+ <label for="title">@Messages("post.new.title")</label> |
|
23 |
+ </dt> |
|
24 |
+ <dd> |
|
25 |
+ @helper.inputText(form("title"), 'class->"text title", 'maxlength -> "250", 'tabindex -> 1) |
|
26 |
+ </dd> |
|
27 |
+ </dl> |
|
28 |
+ |
|
29 |
+ @helper.textarea(form("contents"), 'markdown -> true, 'class->"text content", 'tabindex -> 2) |
|
30 |
+ |
|
31 |
+ @if(UserApp.currentUser() != UserApp.anonymous) { |
|
32 |
+ <div id="upload" resourceType="@ResourceType.BOARD_POST"></div> |
|
27 | 33 |
} |
28 | 34 |
<!-- |
29 | 35 |
<div class="content-footer"> |
... | ... | @@ -61,16 +67,17 @@ |
61 | 67 |
</div>--> |
62 | 68 |
<div class="actions"> |
63 | 69 |
<button class="btn-transparent n-btn orange med">SAVE</button> |
64 |
- <a href="javascript:history.back();return false;" class="n-btn gray med cancel">CANCEL</a> |
|
70 |
+ <a href="javascript:history.back(); return false;" class="n-btn gray med cancel">CANCEL</a> |
|
65 | 71 |
</div> |
66 | 72 |
} |
67 | 73 |
</div> |
68 | 74 |
</div> |
69 |
-<script>nforge.require("board.vaildate");</script> |
|
70 | 75 |
|
71 | 76 |
@views.html.markdown() |
77 |
+ |
|
72 | 78 |
<script type="text/javascript"> |
73 |
-nforge.require('shortcut.submit'); |
|
74 |
-nforge.require('board.new', "@routes.AttachmentApp.newFile"); |
|
79 |
+ nforge.require("board.vaildate"); |
|
80 |
+ nforge.require('shortcut.submit'); |
|
81 |
+ nforge.require('board.new', "@routes.AttachmentApp.newFile"); |
|
75 | 82 |
</script> |
76 | 83 |
} |
--- app/views/board/post.scala.html
+++ app/views/board/post.scala.html
... | ... | @@ -6,14 +6,18 @@ |
6 | 6 |
@implicitField = @{ helper.FieldConstructor(simpleForm) } |
7 | 7 |
|
8 | 8 |
@main("상세보기", project, utils.MenuType.BOARD){ |
9 |
+ |
|
9 | 10 |
<div class="page board-view"> |
10 | 11 |
@views.html.prjmenu(project, utils.MenuType.BOARD, "main-menu-only") |
11 | 12 |
|
13 |
+ @** Post Info **@ |
|
12 | 14 |
<div class="board-header"> |
13 | 15 |
<div class="board-id div">@post.id</div> |
14 | 16 |
<h1 class="title div">@post.title</h1> |
15 | 17 |
<div class="date div">@utils.TemplateHelper.agoString(post.ago())</div> |
16 | 18 |
</div> |
19 |
+ |
|
20 |
+ @** Content body **@ |
|
17 | 21 |
<div class="board-body"> |
18 | 22 |
<div class="author-info"> |
19 | 23 |
<a href="@routes.UserApp.userInfo(post.authorLoginId)" class="pull-left img-rounded"> |
... | ... | @@ -23,21 +27,35 @@ |
23 | 27 |
<a href="@routes.UserApp.userInfo(post.authorLoginId)"><strong>@post.authorLoginId</strong></a> <!--<span class="name">(Loren Brichter)</span>--> |
24 | 28 |
</p> |
25 | 29 |
<p class="status"> |
26 |
- <!--Hit <strong class="num">777</strong> -->Comment <strong class="num">@post.commentCount</strong><!-- Like <i class="ico ico-like-small"></i> <strong class="num">522</strong>--> |
|
30 |
+ <!--Hit <strong class="num">777</strong> |
|
31 |
+ -->Comment <strong class="num">@post.commentCount</strong><!-- |
|
32 |
+ Like <i class="ico ico-like-small"></i> <strong class="num">522</strong>--> |
|
27 | 33 |
</p> |
28 |
- </div> |
|
29 | 34 |
</div> |
30 |
- <div class="content" markdown>@post.contents</div> |
|
31 |
- <div class="attachments" resourceType=@ResourceType.BOARD_POST resourceId=@post.id></div> |
|
32 |
- <!-- |
|
35 |
+ </div> |
|
36 |
+ <div class="content" markdown="true">@post.contents</div> |
|
37 |
+ <div class="attachments" resourceType="@ResourceType.BOARD_POST" resourceId="@post.id"></div> |
|
38 |
+ <!-- |
|
33 | 39 |
<ul class="attaches wm"> |
34 | 40 |
<li class="attach"><i class="ico ico-clip"></i>K23.png (11KB)</li> |
35 | 41 |
<li class="attach"><i class="ico ico-clip"></i>K23.png (11KB)</li> |
36 | 42 |
<li class="attach"><i class="ico ico-clip"></i>K23.png (11KB)</li> |
37 | 43 |
</ul>--> |
38 | 44 |
</div> |
39 |
- <div class="board-comment-wrap"> |
|
45 |
+ <div class="board-footer board-actrow"> |
|
46 |
+ @isAllowed(UserApp.currentUser(), post.asResource(), Operation.UPDATE)){ |
|
47 |
+ <a href="@routes.BoardApp.editPostForm(project.owner, project.name, post.id)" class="n-btn orange med">@Messages("button.edit")</a> |
|
48 |
+ } |
|
49 |
+ @isAllowed(UserApp.currentUser(), post.asResource(), Operation.DELETE)){ |
|
50 |
+ <a href="#deleteConfirm" data-toggle="modal" class="n-btn light-gray med">@Messages("button.delete")</a> |
|
51 |
+ } |
|
52 |
+ <a href="@routes.BoardApp.posts(project.owner, project.name)" class="n-btn gray med">@Messages("button.list")</a> |
|
53 |
+ </div> |
|
54 |
+ |
|
55 |
+@** Comment **@ |
|
56 |
+<div class="board-comment-wrap"> |
|
40 | 57 |
<div class="comment-header"><strong>Comment</strong> <strong class="num">@post.comments.size()</strong></div> |
58 |
+ |
|
41 | 59 |
<ul class="comments"> |
42 | 60 |
@for(comment <-post.comments){ |
43 | 61 |
<li class="comment"> |
... | ... | @@ -61,64 +79,100 @@ |
61 | 79 |
</li> |
62 | 80 |
} |
63 | 81 |
</ul> |
64 |
- @if(isCreatable(User.findByLoginId(session.get("loginId")), project, models.enumeration.ResourceType.BOARD_POST)){ |
|
65 |
- <div class="write-comment-box"> |
|
82 |
+ |
|
83 |
+@if(isCreatable(User.findByLoginId(session.get("loginId")), project, models.enumeration.ResourceType.BOARD_POST)){ |
|
84 |
+ <div class="write-comment-box"> |
|
66 | 85 |
@helper.form(routes.BoardApp.newComment(project.owner, project.name, post.id), 'class->"nm", 'enctype -> "multipart/form-data"){ |
67 |
- <div class="write-comment-wrap"> |
|
68 |
- <!-- when the user signed in.. |
|
69 |
- <textarea class="text comment" name="comment></textarea> |
|
70 |
- --> |
|
71 |
- <style> |
|
72 |
- .write-comment-wrap div[div=preview] { |
|
73 |
- font-size: 12px; |
|
74 |
- width : 729px; |
|
75 |
- min-height: 80px; |
|
76 |
- margin:0px; |
|
77 |
- display: inline-block; |
|
78 |
- vertical-align : middle; |
|
79 |
- } |
|
80 |
- .write-comment-wrap div{font-size: 10px;} |
|
81 |
- </style> |
|
82 |
- @helper.textarea(commentForm("contents"), 'class->"text comment", 'markdown->true) |
|
83 |
- <button class="comment-btn">COMMENT</button> |
|
86 |
+ <div class="write-comment-wrap"> |
|
87 |
+ @helper.textarea(commentForm("contents"), 'class->"text comment", 'markdown->true) |
|
88 |
+ <button class="comment-btn">COMMENT</button> |
|
89 |
+ </div> |
|
90 |
+ |
|
91 |
+ <div class="attach-wrap"> |
|
92 |
+ <div class="thumb-wrap"> |
|
93 |
+ @if(UserApp.currentUser() != UserApp.anonymous) { |
|
94 |
+ <img src="@User.findByLoginId(session.get("loginId")).avatarUrl" class="img-rounded" width="32" height="32" alt="avatar"> |
|
95 |
+ } else { |
|
96 |
+ <img src="@routes.Assets.at("images/default-avatar-34.png")" class="img-rounded" width="32" height="32" alt="avatar"> |
|
97 |
+ } |
|
98 |
+ </div> |
|
99 |
+ |
|
100 |
+ @** fileUploader **@ |
|
84 | 101 |
@if(UserApp.currentUser() != UserApp.anonymous) { |
85 |
- <div id="upload" resourceType=@ResourceType.BOARD_COMMENT></div> |
|
86 |
- } |
|
87 |
- </div> |
|
88 |
- } |
|
89 |
- </div> |
|
90 |
- } |
|
91 |
- </div> |
|
92 |
- <div class="board-footer"> |
|
93 |
- <!--<a href="/add-notification" class="add-btn"><i class="ico ico-plus-blue"></i>자동알림추가</a>--> |
|
94 |
- <a href="@routes.BoardApp.posts(project.owner, project.name)" class="n-btn orange small">@Messages("button.list")</a> |
|
95 |
- @isAllowed(UserApp.currentUser(), post.asResource(), Operation.DELETE)){ |
|
96 |
- <a data-toggle="modal" href="#deleteConfirm" class="n-btn black small">@Messages("button.delete")</a> |
|
97 |
- } |
|
98 |
- @isAllowed(UserApp.currentUser(), post.asResource(), Operation.UPDATE)){ |
|
99 |
- <a href="@routes.BoardApp.editPostForm(project.owner, project.name, post.id)" class="n-btn blue small">@Messages("button.edit")</a> |
|
100 |
- } |
|
101 |
- </div> |
|
102 | 102 |
|
103 |
- <!--삭제확인상자--> |
|
104 |
- <div class="modal hide fade" id="deleteConfirm"> |
|
105 |
- <div class="modal-header"> |
|
106 |
- <button type="button" class="close" data-dismiss="modal">×</button> |
|
107 |
- <h3>확인</h3> |
|
108 |
- </div> |
|
109 |
- <div class="modal-body"> |
|
110 |
- <p>게시글이 삭제되며 영원히 복구할수 없습니다.</p> |
|
111 |
- <p>그래도 삭제하시겠습니까?</p> |
|
112 |
- </div> |
|
113 |
- <div class="modal-footer"> |
|
114 |
- <a class="n-btn red small" href="@routes.BoardApp.deletePost(project.owner, project.name, post.id)">예</a> |
|
115 |
- <a href="#" class="n-btn blue small" data-dismiss="modal">아니오</a> |
|
116 |
- </div> |
|
103 |
+ <div id="upload" class="attach-info-wrap" resourceType="@ResourceType.BOARD_COMMENT"> |
|
104 |
+ <div> |
|
105 |
+ <span class="progress-num">0%</span> <span class="sp-line"> </span> |
|
106 |
+ <strong>total</strong> <span class="total-num">0MB</span> |
|
107 |
+ </div> |
|
108 |
+ <div class="progress-wrap"> |
|
109 |
+ <div class="progress n4"> |
|
110 |
+ <div class="bar orange" style="width: 0%;"></div> |
|
111 |
+ </div> |
|
112 |
+ </div> |
|
113 |
+ <!-- <a href="#!/cancel"><i class="ico btn-cancel"></i></a> --> |
|
114 |
+ </div> |
|
115 |
+ |
|
116 |
+ <div class="btn-wrap"> |
|
117 |
+ <div class="ns-btn fake-file-wrap"> |
|
118 |
+ <i class="ico ico-plus-blue"></i>UPLOAD <input type="file" class="file" name="filePath"> |
|
119 |
+ </div> |
|
120 |
+ </div> |
|
121 |
+ } |
|
122 |
+ @** end of fileUploader **@ |
|
123 |
+ |
|
124 |
+ </div> |
|
125 |
+ } @** end of comment form **@ |
|
126 |
+ <div class="attached-files-wrap"> |
|
127 |
+ <ul class="attached-files"></ul> |
|
128 |
+ </div> |
|
117 | 129 |
</div> |
130 |
+ <script type="text/template" id="tplAttachedFile"><!-- |
|
131 |
+ --><li class="attached-file" data-name="${fileName}" data-href="${fileHref}" data-mime="${mimeType}" data-size="${fileSize}"> |
|
132 |
+ <strong>${fileName}(${fileSizeReadable})</strong><!-- |
|
133 |
+ --><a class="attached-delete"><i class="ico btn-delete"></i></a> |
|
134 |
+ </li> |
|
135 |
+ </script> |
|
136 |
+} @** end of write-comment-box **@ |
|
137 |
+ |
|
118 | 138 |
</div> |
119 |
-@views.html.markdown() |
|
120 |
-<script type="text/javascript"> |
|
121 |
- nforge.require('shortcut.submit'); |
|
122 |
- nforge.require('board.view', "@routes.AttachmentApp.newFile"); |
|
123 |
-</script> |
|
139 |
+ |
|
140 |
+<div class="board-footer"> |
|
141 |
+ |
|
142 |
+@isAllowed(UserApp.currentUser(), post.asResource(), Operation.UPDATE)){ |
|
143 |
+ <a href="@routes.BoardApp.editPostForm(project.owner, project.name, post.id)" class="n-btn orange med">@Messages("button.edit")</a> |
|
124 | 144 |
} |
145 |
+ |
|
146 |
+@isAllowed(UserApp.currentUser(), post.asResource(), Operation.DELETE)){ |
|
147 |
+ <a href="#deleteConfirm" data-toggle="modal" class="n-btn light-gray med">@Messages("button.delete")</a> |
|
148 |
+} |
|
149 |
+ |
|
150 |
+ <a href="@routes.BoardApp.posts(project.owner, project.name)" class="n-btn gray med">@Messages("button.list")</a> |
|
151 |
+ |
|
152 |
+</div> |
|
153 |
+ |
|
154 |
+@** Confirm to delete post **@ |
|
155 |
+@** TODO: 메시지 다국어 처리할 것 **@ |
|
156 |
+<div id="deleteConfirm" class="modal hide fade"> |
|
157 |
+ <div class="modal-header"> |
|
158 |
+ <button type="button" class="close" data-dismiss="modal">×</button> |
|
159 |
+ <h3>확인</h3> |
|
160 |
+ </div> |
|
161 |
+ <div class="modal-body"> |
|
162 |
+ <p>게시글이 삭제되며 영원히 복구할수 없습니다.</p> |
|
163 |
+ <p>그래도 삭제하시겠습니까?</p> |
|
164 |
+ </div> |
|
165 |
+ <div class="modal-footer"> |
|
166 |
+ <a class="btn btn-danger med" |
|
167 |
+ href="@routes.BoardApp.deletePost(project.owner, project.name, post.id)">예</a> |
|
168 |
+ <a href="#" class="btn med" data-dismiss="modal">아니오</a> |
|
169 |
+ </div> |
|
170 |
+</div> |
|
171 |
+ |
|
172 |
+@views.html.markdown() |
|
173 |
+ |
|
174 |
+<script type="text/javascript"> |
|
175 |
+ nforge.require('shortcut.submit'); |
|
176 |
+ nforge.require('board.view', "@routes.AttachmentApp.newFile"); |
|
177 |
+</script> |
|
178 |
+}(No newline at end of file) |
--- app/views/board/postList.scala.html
+++ app/views/board/postList.scala.html
... | ... | @@ -6,7 +6,7 @@ |
6 | 6 |
@import utils.AccessControl._ |
7 | 7 |
@import scala.collection.immutable.Map |
8 | 8 |
|
9 |
-@header(label:String, key:String) = { |
|
9 |
+@** header(label:String, key:String) = { |
|
10 | 10 |
<th> |
11 | 11 |
<a key="@key" href="@routes.BoardApp.posts(project.owner, project.name)">@label</a> |
12 | 12 |
@if(key == param.key){ |
... | ... | @@ -18,6 +18,7 @@ |
18 | 18 |
} |
19 | 19 |
</th> |
20 | 20 |
} |
21 |
+**@ |
|
21 | 22 |
|
22 | 23 |
@main(title, project, utils.MenuType.BOARD) { |
23 | 24 |
<div class="page"> |
... | ... | @@ -37,26 +38,27 @@ |
37 | 38 |
</div> |
38 | 39 |
|
39 | 40 |
@if(page.getTotalRowCount == 0){ |
41 |
+ |
|
42 |
+ <div class="error-wrap"> |
|
43 |
+ <i class="ico ico-err1"></i> |
|
44 |
+ <p>@Messages("post.is.empty")</p> |
|
45 |
+ </div> |
|
40 | 46 |
<div> |
41 |
- <p class="emptyMessage">@Messages("post.is.empty")</p> |
|
42 |
- </div> |
|
47 |
+ |
|
43 | 48 |
} else { |
49 |
+ |
|
44 | 50 |
<div class="filter-wrap board"> |
45 | 51 |
<div class="filters" id="order"> |
46 |
- <a href="#" key="date" class="filter underConstruction"> |
|
47 |
- @if(param.key == "date"){ |
|
48 |
- <i class="ico btn-gray-arrow @if(param.order == "desc"){down}"></i> |
|
49 |
- } |
|
50 |
- 날짜순 |
|
52 |
+ <a href="#" key="date" class="filter underConstruction @if(param.key == "date"){ active }"> |
|
53 |
+ <i class="ico btn-gray-arrow @if(param.key == "date" && param.order == "desc"){down}"></i>날짜순 |
|
51 | 54 |
</a> |
52 |
- <a href="#" key="commentCount" class="filter active underConstruction"> |
|
53 |
- @if(param.key == "commentCount"){ |
|
54 |
- <i class="ico btn-gray-arrow @if(param.order == "desc"){down}"></i> |
|
55 |
- } |
|
56 |
- 댓글순 |
|
55 |
+ |
|
56 |
+ <a href="#" key="commentCount" class="filter underConstruction @if(param.key == "commentCount"){ active }"> |
|
57 |
+ <i class="ico btn-gray-arrow @if(param.key == "commentCount" && param.order == "desc"){down}"></i>댓글순 |
|
57 | 58 |
</a> |
58 | 59 |
</div> |
59 | 60 |
</div> |
61 |
+ |
|
60 | 62 |
<ul class="board-list"> |
61 | 63 |
@for(post <- page.getList()){ |
62 | 64 |
<li class="board"> |
... | ... | @@ -77,20 +79,24 @@ |
77 | 79 |
} |
78 | 80 |
</ul> |
79 | 81 |
} |
82 |
+ |
|
80 | 83 |
@if(isCreatable(User.findByLoginId(session.get("loginId")), project, models.enumeration.ResourceType.BOARD_POST)){ |
81 | 84 |
<div class="write-btn-wrap"> |
82 | 85 |
<a href="@routes.BoardApp.newPostForm(project.owner, project.name)" class="n-btn orange med">@Messages("post.write")</a> |
83 | 86 |
</div> |
84 | 87 |
} |
88 |
+ |
|
85 | 89 |
<div id="pagination"> |
86 | 90 |
<!-- pagination.js will fill here. --> |
87 | 91 |
</div> |
88 | 92 |
</div> |
89 |
- <script src="@getJSLink("pagination")" type="text/javascript"></script> |
|
90 |
- <script type="text/javascript"> |
|
93 |
+ |
|
94 |
+<script src="@getJSLink("pagination")" type="text/javascript"></script> |
|
95 |
+<script type="text/javascript"> |
|
91 | 96 |
nforge.require('board.list'); |
92 | 97 |
$(document).ready(function() { |
93 | 98 |
Pagination.update($('#pagination'), @page.getTotalPageCount); |
94 | 99 |
}); |
95 |
- </script> |
|
100 |
+</script> |
|
101 |
+ |
|
96 | 102 |
} |
--- app/views/layout.scala.html
+++ app/views/layout.scala.html
... | ... | @@ -32,8 +32,9 @@ |
32 | 32 |
<script src="@getJSLink("humanize")" type="text/javascript"></script> |
33 | 33 |
<script src="@getJSLink("validate")" type="text/javascript"></script> |
34 | 34 |
<script src="@getJSLink("jquery.zclip.min")" type="text/javascript"></script> |
35 |
- <script src="@getJSLink("uploader")" type="text/javascript"></script> |
|
35 |
+ <script src="@getJSLink("fileUploader")" type="text/javascript"></script> |
|
36 | 36 |
<script src="@getJSLink("jquery.placeholder.min")" type="text/javascript"></script> |
37 |
+ <script src="@getJSLink("jquery.tmpl")" type="text/javascript"></script> |
|
37 | 38 |
<script src="@routes.Application.jsMessages()" type="text/javascript"></script> |
38 | 39 |
</head> |
39 | 40 |
|
... | ... | @@ -55,5 +56,6 @@ |
55 | 56 |
$(".underConstruction").tooltip({placement: 'left', title: 'Sorry! Under construction...'}); |
56 | 57 |
}); |
57 | 58 |
</script> |
59 |
+ |
|
58 | 60 |
</body> |
59 | 61 |
</html> |
--- app/views/login.scala.html
+++ app/views/login.scala.html
... | ... | @@ -11,7 +11,7 @@ |
11 | 11 |
<form action="@routes.UserApp.login()" method="POST"> |
12 | 12 |
<dl> |
13 | 13 |
<dt> |
14 |
- <label for="email">@Messages("user.loginId")</label> |
|
14 |
+ <label for="loginId">@Messages("user.loginId")</label> |
|
15 | 15 |
</dt> |
16 | 16 |
<dd> |
17 | 17 |
<input type="text" class="text email" id="loginId" autocomplete="off" name="loginId"> |
--- app/views/milestone/create.scala.html
+++ app/views/milestone/create.scala.html
... | ... | @@ -1,66 +1,104 @@ |
1 |
-@(title:String, form: Form[Milestone], projectInst: Project) |
|
2 |
- |
|
3 |
- |
|
4 |
-@import utils.TemplateHelper._ |
|
5 |
-@project.projectMngMain(title, projectInst) { |
|
6 |
- @pageTitle(projectInst,"Milestone") |
|
7 |
- <div class="form-wrap milestone"> |
|
8 |
- <form class="nm" action="@routes.MilestoneApp.newMilestone(projectInst.owner, projectInst.name)" method="post"> |
|
9 |
- <div class="inner left"> |
|
10 |
- <div class="n-alert hide" id="title_error"> |
|
11 |
- <div class="n-inner"> |
|
12 |
- <span class="msg">타이틀을 입력해주세요.</span> |
|
13 |
- <a href="#!/close" class="ico btn-delete"></a> |
|
14 |
- </div> |
|
15 |
- </div> |
|
16 |
- <label for="title"> |
|
17 |
- <input type="text" name="title" id="title" class="text" placeholder="새 마일스톤의 제목을 입력해주세요."> |
|
18 |
- </label> |
|
19 |
- <div class="n-alert hide" id="contents_error"> |
|
20 |
- <div class="n-inner"> |
|
21 |
- <span class="msg">내용을 입력해주세요.</span> |
|
22 |
- <a href="#!/close" class="ico btn-delete"></a> |
|
23 |
- </div> |
|
24 |
- </div> |
|
25 |
- <label for="contents"> |
|
26 |
- <textarea class="textarea" id="contents" name="contents" placeholder="내용을 입력해주세요."></textarea> |
|
27 |
- </label> |
|
28 |
- <div class="actions"> |
|
29 |
- <button type="submit" class="n-btn blue med btn-transparent save">SAVE</button> |
|
30 |
- <a href="@routes.MilestoneApp.manageMilestones(projectInst.owner, projectInst.name)" class="n-btn gray med">CANCEL</a> |
|
31 |
- </div> |
|
32 |
- </div> |
|
33 |
- <div class="inner right"> |
|
34 |
- <div class="cu-label">상태</div> |
|
35 |
- <div class="cu-desc"> |
|
36 |
- <input name="state" type="radio" checked class="radio-btn" value=@State.OPEN><label for="state" class="bg-radiobtn">Open</label> |
|
37 |
- <input name="state" type="radio" class="radio-btn" value=@State.CLOSED><label for="state" class="bg-radiobtn">Closed</label> |
|
38 |
- </div> |
|
39 |
- <hr/> |
|
40 |
- <p>완료일을 선택하세요.</p> |
|
41 |
- <div class="n-alert hide" id="dueDate_error"> |
|
42 |
- <div class="n-inner"> |
|
43 |
- <span class="msg">완료일을 입력해주세요.</span> |
|
44 |
- <a href="#!/close" class="ico btn-delete"></a> |
|
45 |
- </div> |
|
46 |
- </div> |
|
47 |
- <label for="dueDate"> |
|
48 |
- <input type="text" name="dueDate" id="dueDate" class="validate due-date"> |
|
49 |
- </label> |
|
50 |
- </div> |
|
51 |
- </form> |
|
52 |
- </div> |
|
53 |
- |
|
54 |
- <script type="text/javascript">nforge.require('milestone.manage');</script> |
|
55 |
- <style> |
|
56 |
- @@IMPORT url(@getCSSLink("pikaday")); |
|
57 |
- </style> |
|
58 |
- <script src="@getJSLink("moment.min")" type="text/javascript"></script> |
|
59 |
- <script src="@getJSLink("pikaday/pikaday")" type="text/javascript"></script> |
|
60 |
- <script> |
|
61 |
- var picker = new Pikaday({ |
|
62 |
- field: document.getElementById('dueDate'), |
|
63 |
- format: 'YYYY-MM-DD', |
|
64 |
- }); |
|
65 |
- </script> |
|
66 |
-} |
|
1 |
+@(title:String, form: Form[Milestone], projectInst: Project) |
|
2 |
+ |
|
3 |
+ |
|
4 |
+@import utils.TemplateHelper._ |
|
5 |
+@project.projectMngMain(title, projectInst) { |
|
6 |
+ @views.html.prjmenu(projectInst, utils.MenuType.PROJECT_SETTING, "") |
|
7 |
+ |
|
8 |
+ <div class="form-wrap milestone frm-wrap"> |
|
9 |
+ <form class="nm" action="@routes.MilestoneApp.newMilestone(projectInst.owner, projectInst.name)" method="post"> |
|
10 |
+ <div class="inner left"> |
|
11 |
+ <dl> |
|
12 |
+ <dt> |
|
13 |
+ <div class="n-alert hide" id="title_error"> |
|
14 |
+ <div class="n-inner"> |
|
15 |
+ <span class="msg">타이틀을 입력해주세요.</span> |
|
16 |
+ <a href="#!/close" class="ico btn-delete"></a> |
|
17 |
+ </div> |
|
18 |
+ </div> |
|
19 |
+ <label for="title">새 마일스톤의 제목을 입력해주세요</label> |
|
20 |
+ </dt> |
|
21 |
+ <dd> |
|
22 |
+ <input type="text" name="title" id="title" class="text" placeholder=""> |
|
23 |
+ </dd> |
|
24 |
+ |
|
25 |
+ <dt> |
|
26 |
+ <div class="n-alert hide" id="contents_error"> |
|
27 |
+ <div class="n-inner"> |
|
28 |
+ <span class="msg">내용을 입력해주세요.</span> |
|
29 |
+ <a href="#!/close" class="ico btn-delete"></a> |
|
30 |
+ </div> |
|
31 |
+ </div> |
|
32 |
+ <label for="contents">내용을 입력해주세요</label> |
|
33 |
+ </dt> |
|
34 |
+ <dd> |
|
35 |
+ <textarea class="textarea" id="contents" name="contents"></textarea> |
|
36 |
+ </dd> |
|
37 |
+ </dl> |
|
38 |
+ |
|
39 |
+ <div class="actions"> |
|
40 |
+ <button type="submit" class="n-btn orange med btn-transparent save">SAVE</button> |
|
41 |
+ <a href="@routes.MilestoneApp.manageMilestones(projectInst.owner, projectInst.name)" class="n-btn gray med">CANCEL</a> |
|
42 |
+ </div> |
|
43 |
+ </div> |
|
44 |
+ |
|
45 |
+ <div class="inner right bubble-wrap dark-gray"> |
|
46 |
+ <p>마일스톤 상태</p> |
|
47 |
+ <div> |
|
48 |
+ <input type="radio" name="state" value="@State.OPEN" id="milestone-open" class="radio-btn" checked="checked"><label for="milestone-open" class="bold">Open</label> |
|
49 |
+ |
|
50 |
+ <input type="radio" name="state" value="@State.CLOSED" id="milestone-close" class="radio-btn"><label for="milestone-close" class="bold">Closed</label> |
|
51 |
+ </div> |
|
52 |
+ <hr/> |
|
53 |
+ <p>완료일을 선택하세요.</p> |
|
54 |
+ <div class="n-alert hidden"> |
|
55 |
+ <div class="n-inner"> |
|
56 |
+ <span class="msg">완료일을 입력해주세요.</span> |
|
57 |
+ <a href="#!/close" class="ico btn-delete"></a> |
|
58 |
+ </div> |
|
59 |
+ </div> |
|
60 |
+ <label for="dueDate"> |
|
61 |
+ <input type="text" name="dueDate" id="dueDate" class="validate due-date"> |
|
62 |
+ </label> |
|
63 |
+ <div id="datepicker" class="date-picker"></div> |
|
64 |
+ |
|
65 |
+ </div> |
|
66 |
+ </form> |
|
67 |
+ </div> |
|
68 |
+ |
|
69 |
+ <script type="text/javascript">nforge.require('milestone.manage');</script> |
|
70 |
+ <style type="text/css"> |
|
71 |
+ @@IMPORT url(@getCSSLink("pikaday")); |
|
72 |
+ </style> |
|
73 |
+ <script src="@getJSLink("moment.min")" type="text/javascript"></script> |
|
74 |
+ <script src="@getJSLink("pikaday/pikaday")" type="text/javascript"></script> |
|
75 |
+ |
|
76 |
+ <script type="text/javascript"> |
|
77 |
+ $(document).ready(function(){ |
|
78 |
+ |
|
79 |
+ // 날짜 선택 영역 설정 |
|
80 |
+ // @@requires Pikaday (https://github.com/dbushell/Pikaday) |
|
81 |
+ (function(sInputId, sContainer){ |
|
82 |
+ var elInput = $(sInputId); |
|
83 |
+ var elContainer = $(sContainer); |
|
84 |
+ var sDateFormat = "YYYY-MM-DD"; |
|
85 |
+ |
|
86 |
+ var oPicker = new Pikaday({ |
|
87 |
+ "format": sDateFormat, |
|
88 |
+ "onSelect": function(oDate){ |
|
89 |
+ elInput.val(this.toString()); |
|
90 |
+ } |
|
91 |
+ }); |
|
92 |
+ |
|
93 |
+ elContainer.append(oPicker.el); |
|
94 |
+ |
|
95 |
+ elInput.attr("value", oPicker.getMoment().format(sDateFormat)); |
|
96 |
+ elInput.blur(function(){ |
|
97 |
+ oPicker.setDate(this.value); |
|
98 |
+ }); |
|
99 |
+ |
|
100 |
+ })("#dueDate", "#datepicker"); |
|
101 |
+ |
|
102 |
+ }); |
|
103 |
+</script> |
|
104 |
+} |
--- app/views/project/memberList.scala.html
+++ app/views/project/memberList.scala.html
... | ... | @@ -16,8 +16,8 @@ |
16 | 16 |
<div class="bubble-wrap dark-gray wp"> |
17 | 17 |
<div class="inner-bubble"> |
18 | 18 |
<form class="nm" action="@routes.ProjectApp.newMember(project.owner, project.name)" method="post" id="addNewMember"> |
19 |
- <input type="text" class="text uname" id="loginId" name="loginId" placeholder="@Messages("project.members.addMember")" pattern="[a-zA-Z0-9_][a-zA-Z0-9_]+" title="@Messages("user.wrongloginId.alert")" /> |
|
20 |
- <button class="ns-btn" type="submit"><i class="ico ico-plus-blue"></i>@Messages("button.add")</button> |
|
19 |
+ <input type="text" class="text uname" id="loginId" name="loginId" placeholder="@Messages("project.members.addMember")" pattern="[a-zA-Z0-9_][a-zA-Z0-9_]+" title="@Messages("user.wrongloginId.alert")" /><!-- |
|
20 |
+ --><button class="ns-btn" type="submit"><i class="ico ico-plus-blue"></i>@Messages("button.add")</button> |
|
21 | 21 |
</form> |
22 | 22 |
</div> |
23 | 23 |
|
--- app/views/project/setting.scala.html
+++ app/views/project/setting.scala.html
... | ... | @@ -11,7 +11,7 @@ |
11 | 11 |
<div class="bubble-wrap gray"> |
12 | 12 |
@form(action=routes.ProjectApp.settingProject(project.owner, project.name), 'id->"saveSetting" , 'enctype->"multipart/form-data", 'class->"nm"){ |
13 | 13 |
<input type="hidden" name="id" value="@projectForm("id").value.toLong"> |
14 |
- <div class="box-wrap top clearfix"> |
|
14 |
+ <div class="box-wrap top clearfix frm-wrap"> |
|
15 | 15 |
<div class="setting-box left"> |
16 | 16 |
<div class="logo-wrap"> |
17 | 17 |
@if(projectForm("logoPath").value != null) { |
... | ... | @@ -25,7 +25,7 @@ |
25 | 25 |
<li><strong>@Messages("project.logo")</strong></li> |
26 | 26 |
<li>@Messages("project.logo.type") <span class="point">bmp, jpg, gif, png</span></li> |
27 | 27 |
<li>@Messages("project.logo.maxFileSize") <span class="point">1MB</span></li> |
28 |
- <li>@Messages("project.logo.size") <span class="point">234px * 168px</span></li> |
|
28 |
+ <li>@Messages("project.logo.size") <span class="point">234px × 168px</span></li> |
|
29 | 29 |
<li> |
30 | 30 |
<div class="ns-btn fake-file-wrap"><i class="ico ico-plus-blue"></i>UPLOAD |
31 | 31 |
<input type="file" class="file" name="logoPath"> |
... | ... | @@ -34,26 +34,33 @@ |
34 | 34 |
</ul> |
35 | 35 |
</div> |
36 | 36 |
</div> |
37 |
- <div class="setting-box right"> |
|
38 |
- <div class="n-alert hide" id="alert_msg"> |
|
39 |
- <div class="n-inner"> |
|
40 |
- <span class="msg">@Messages("project.wrongName")</span> |
|
41 |
- <a href="#" class="ico btn-delete" data-dismiss="alert"></a> |
|
42 |
- </div> |
|
43 |
- </div> |
|
44 |
- <label for="project-name"> |
|
45 |
- @inputText(projectForm("name"), 'class->"text", 'placeholder->Messages("project.name.placeholder"), 'maxlength -> "250") |
|
46 |
- </label> |
|
47 |
- <label for="project-desc"> |
|
48 |
- @textarea(projectForm("overview"), 'placeholder->Messages("project.description.placeholder"), 'class->"textarea") |
|
49 |
- </label> |
|
50 |
- </div> |
|
37 |
+ <dl class="setting-box right"> |
|
38 |
+ <dt> |
|
39 |
+ <div class="n-alert hide" id="alert_msg"> |
|
40 |
+ <div class="n-inner"> |
|
41 |
+ <span class="msg">@Messages("project.wrongName")</span> |
|
42 |
+ <a href="#" class="ico btn-delete" data-dismiss="alert"></a> |
|
43 |
+ </div> |
|
44 |
+ </div> |
|
45 |
+ <label for="project-name">@Messages("project.name.placeholder")</label> |
|
46 |
+ </dt> |
|
47 |
+ <dd> |
|
48 |
+ @inputText(projectForm("name"), 'class->"text", 'maxlength -> "250") |
|
49 |
+ </dd> |
|
50 |
+ |
|
51 |
+ <dt> |
|
52 |
+ <label for="project-desc">@Messages("project.description.placeholder")</label> |
|
53 |
+ </dt> |
|
54 |
+ <dd> |
|
55 |
+ @textarea(projectForm("overview"), 'class->"textarea") |
|
56 |
+ </dd> |
|
57 |
+ </dl> |
|
51 | 58 |
</div> |
52 | 59 |
<div class="box-wrap middle"> |
53 | 60 |
<div class="cu-label">@Messages("project.shareOption")</div> |
54 | 61 |
<div class="cu-desc"> |
55 |
- <input name="share_option" type="radio" @if(project.share_option == true){checked="checked"} id="public" value="true" class="radio-btn"><label for="public" class="bg-radiobtn">@Messages("project.public")</label> |
|
56 |
- <input name="share_option" type="radio" @if(project.share_option == false){checked="checked"} id="private" value="false" class="radio-btn"><label for="private" class="bg-radiobtn">@Messages("project.private")</label> |
|
62 |
+ <input name="share_option" type="radio" @if(project.share_option == true){checked="checked"} id="public" value="true" class="radio-btn"><label for="public" class="bg-radiobtn label-public">@Messages("project.public")</label> |
|
63 |
+ <input name="share_option" type="radio" @if(project.share_option == false){checked="checked"} id="private" value="false" class="radio-btn"><label for="private" class="bg-radiobtn label-private">@Messages("project.private")</label> |
|
57 | 64 |
<span class="note">@Messages("project.private.notice")</span> |
58 | 65 |
</div> |
59 | 66 |
</div> |
... | ... | @@ -75,7 +82,7 @@ |
75 | 82 |
<div class="cu-label">@Messages("project.delete")</div> |
76 | 83 |
<div class="cu-desc"> |
77 | 84 |
<p><strong class="notice">@Messages("project.delete.description")</strong></p> |
78 |
- <p><input type="checkbox" class="checkbox" autocomplete="off" id="accept"><label for="agreement" class="bg-checkbox">@Messages("project.delete.accept")</label> |
|
85 |
+ <p><input type="checkbox" class="checkbox" autocomplete="off" id="accept"><label for="agreement" class="bg-checkbox label-agreement">@Messages("project.delete.accept")</label> |
|
79 | 86 |
<a id="deletion" data-toggle="modal" href="#alertDeletion" class="n-btn small black"><i class="ico ico-delete-small"></i>@Messages("project.delete.this")</a> |
80 | 87 |
</p> |
81 | 88 |
</div> |
--- app/views/user/info.scala.html
+++ app/views/user/info.scala.html
... | ... | @@ -5,18 +5,16 @@ |
5 | 5 |
@home("Users Info", utils.MenuType.USER) { |
6 | 6 |
@if(user != null && session != null && user.loginId == session.get("loginId")){ |
7 | 7 |
<div class="side-menu-wrap"> |
8 |
- <ul class="side-menus ico bg-side-menu unstyled"> |
|
8 |
+ <ul class="side-menus unstyled bg"> |
|
9 | 9 |
<li class="side-menu"><a href="@routes.UserApp.editUserInfoForm()"><i class="ico ico-setting on"></i></a></li> |
10 | 10 |
</ul> |
11 |
- </div> |
|
11 |
+ </div> |
|
12 | 12 |
} |
13 |
+ |
|
13 | 14 |
|
14 | 15 |
<div class="page-wrap container"> |
15 | 16 |
<div class="page"> |
16 |
- <div> |
|
17 |
- <div><h1 class="page-title">@user.loginId</h1></div> |
|
18 |
- </div> |
|
19 |
- |
|
17 |
+ <h1 class="page-title"><span class="gray">HIVE/</span>@user.loginId</h1> |
|
20 | 18 |
<section class="user-box"> |
21 | 19 |
<div class="user-info-box"> |
22 | 20 |
<div class="whoami-wrap"> |
... | ... | @@ -25,55 +23,76 @@ |
25 | 23 |
<img src="@user.avatarUrl" width="127" height="127" class="img-rounded"> |
26 | 24 |
</div> |
27 | 25 |
<div class="user-location info-box"> |
28 |
- <p class="u-location"><i class="ico ico-location"></i>@user.email</p> |
|
26 |
+ <p class="u-location"><i class="ico ico-location"></i>@user.email<!-- Seoul, Korea, South --></p> |
|
29 | 27 |
</div> |
30 | 28 |
<hr/> |
31 |
- <div class="user-other-info info-box"> |
|
32 |
- <p class="since"><strong>MEMBER SINCE</strong></p> |
|
33 |
- <p><span class="since">@user.getDateString</span></p> |
|
34 |
- <p class="social-btns"> |
|
35 |
- <a href="http://twitter.com/nforge"><i class="ico btn-tw"></i></a> |
|
36 |
- <a href="http://facebook.com/nforge"><i class="ico btn-fb"></i></a> |
|
37 |
- <a href="http://me2.com/nforge"><i class="ico btn-m2"></i></a> |
|
38 |
- </p> |
|
39 |
- </div> |
|
40 |
- <hr/> |
|
41 |
- <div class="btn-wrap"> |
|
42 |
- <a href="/projectform"><i class="ico btn-new-project"></i></a> |
|
43 |
- </div> |
|
44 |
- </div> |
|
45 |
- <div class="user-stream-box"> |
|
46 |
- <ul class="nav nav-tabs user-stream-tab"> |
|
47 |
- <li class="active">Repositories</li> |
|
48 |
- <!-- li><a href="user-activities.html">Activities</a></li--> |
|
49 |
- </ul> |
|
50 |
- <!-- div class="user-stream-wrap"> |
|
51 |
- <div class="filter-wrap user-setting"> |
|
52 |
- <div class="filters"> |
|
53 |
- <a href="/html/user-setting.html" class="filter"><i class="ico btn-gray-arrow"></i>전체</a> |
|
54 |
- <a href="/html/user-activities.html?order=staus" class="filter active"><i class="ico btn-gray-arrow down"></i>상태순</a> |
|
55 |
- </div> |
|
56 |
- </div> |
|
57 |
- </div--> |
|
58 |
- <ul class="user-streams"> |
|
59 |
- @for(project <- user.myProjects()){ |
|
60 |
- <li class="user-stream"> |
|
61 |
- <h3 class="project-name"><a href="@routes.ProjectApp.project(project.owner, project.name)">@project.owner/@project.name</a></h3> |
|
62 |
- <div class="stream-desc-wrap"> |
|
63 |
- <div class="stream-desc"> |
|
64 |
- <!-- p class="nm">최근 활동 관련 영역입니다.</p--> |
|
65 |
- <p class="date">Last updated @agoString(project.ago)</p> |
|
66 |
- </div> |
|
67 |
- <!-- div class="project-status"> |
|
68 |
- <i class="ico ico-like"></i><span class="num">1</span> |
|
69 |
- <i class="ico ico-activity high"></i> |
|
70 |
- </div--> |
|
71 |
- </div> |
|
72 |
- </li> |
|
73 |
- } |
|
74 |
- </ul> |
|
75 |
- </div> |
|
76 |
- </section> |
|
29 |
+ <div class="user-status info-box"> |
|
30 |
+ <p><span class="labels">My projects</span><span class="num">@user.myProjects().length</span></p> |
|
31 |
+ <p><span class="labels">Followers</span><span class="num">0</span></p> |
|
32 |
+ <p><span class="labels">Following</span><span class="num">0</span></p> |
|
33 |
+ <p><span class="labels">Starred</span><span class="num">0</span></p> |
|
77 | 34 |
</div> |
35 |
+ <hr/> |
|
36 |
+ <div class="user-other-info info-box"> |
|
37 |
+ <p><strong>FOCUS</strong></p> |
|
38 |
+ <p class="focuses">C#, JAVA, SQL, JAVASCRIPT</p> |
|
39 |
+ <p class="since"><strong>MEMBER SINCE</strong></p> |
|
40 |
+ <p><span class="since">@user.getDateString</span></p> |
|
41 |
+ <p class="social-btns"> |
|
42 |
+ <a href="http://twitter.com/@user.loginId"><i class="ico btn-tw"></i></a> |
|
43 |
+ <a href="http://facebook.com/@user.loginId"><i class="ico btn-fb"></i></a> |
|
44 |
+ <a href="http://me2day.net/@user.loginId"><i class="ico btn-m2"></i></a> |
|
45 |
+ </p> |
|
46 |
+ </div> |
|
47 |
+ <hr/> |
|
48 |
+ <div class="btn-wrap-new-project"> |
|
49 |
+ <a href="@routes.ProjectApp.newProjectForm()"><i class="ico ico-pencil"></i> Create your project</a> |
|
50 |
+ </div> |
|
78 | 51 |
</div> |
52 |
+<div class="user-stream-box"> |
|
53 |
+ <ul class="nav nav-tabs user-stream-tab hive-tabs"> |
|
54 |
+ <li class="active"><a href="#">Repositories</a></li> |
|
55 |
+ <li><a href="#">Activities</a></li> |
|
56 |
+ </ul> |
|
57 |
+ <div class="user-stream-wrap"> |
|
58 |
+ <div class="header-wrap"> |
|
59 |
+ <div class="search-wrap user-setting"> |
|
60 |
+ <div class="inner"> |
|
61 |
+ <form action="/activity-search" method="get"> |
|
62 |
+ <input name="query" class="text" type="text" placeholder="검색"><button type="submit" class="btn search-btn">SEARCH</button> |
|
63 |
+ </form> |
|
64 |
+ </div> |
|
65 |
+ </div> |
|
66 |
+ </div> |
|
67 |
+ <div class="filter-wrap user-setting"> |
|
68 |
+ <div class="filters"> |
|
69 |
+ <a href="/html/user-setting.html" class="filter"><i class="ico btn-gray-arrow"></i>전체</a> |
|
70 |
+ <a href="/html/user-activities.html?order=staus" class="filter active"><i class="ico btn-gray-arrow down"></i>상태순</a> |
|
71 |
+ </div> |
|
72 |
+ </div> |
|
73 |
+ </div> |
|
74 |
+ <ul class="user-streams"> |
|
75 |
+ @for(project <- user.myProjects()){ |
|
76 |
+ |
|
77 |
+ <li class="user-stream"> |
|
78 |
+ <h3 class="project-name"><a href="@routes.ProjectApp.project(project.owner, project.name)">@project.owner/@project.name</a></h3> |
|
79 |
+ <div class="stream-desc-wrap"> |
|
80 |
+ <div class="stream-desc"> |
|
81 |
+ <p class="nm">@project.overview</p> |
|
82 |
+ <p class="date">Last updated @agoString(project.ago)</p> |
|
83 |
+ </div> |
|
84 |
+ <div class="project-status"> |
|
85 |
+ <i class="ico ico-like"></i><span class="num">1</span> |
|
86 |
+ <i class="ico ico-activity high"></i> |
|
87 |
+ </div> |
|
88 |
+ </div> |
|
89 |
+ </li> |
|
90 |
+ |
|
91 |
+ } |
|
92 |
+ </ul> |
|
93 |
+</div> |
|
94 |
+</section> |
|
95 |
+</div> |
|
96 |
+</div> |
|
97 |
+ |
|
79 | 98 |
} |
--- app/views/user/signup.scala.html
+++ app/views/user/signup.scala.html
... | ... | @@ -8,23 +8,45 @@ |
8 | 8 |
<p class="tag-line">Software development platform for Open Source project.</p> |
9 | 9 |
</div> |
10 | 10 |
|
11 |
- <div class="signup-form-wrap"> |
|
11 |
+ <div class="signup-form-wrap frm-wrap"> |
|
12 | 12 |
<form action="@routes.UserApp.newUser()" method="POST" name="signup"> |
13 |
- <label for="loginId"> |
|
14 |
- <input type="text" class="text password" id="loginId" placeholder="@Messages("user.signupId")" autocomplete="off" name="loginId"> |
|
15 |
- </label> |
|
16 |
- <label for="uname"> |
|
17 |
- <input type="text" class="text password" id="uname" placeholder="@Messages("user.name")" autocomplete="off" name="name"> |
|
18 |
- </label> |
|
19 |
- <label for="email"> |
|
20 |
- <input type="text" class="text password" id="email" placeholder="@Messages("user.email")" autocomplete="off" name="email"> |
|
21 |
- </label> |
|
22 |
- <label for="password"> |
|
23 |
- <input type="password" class="text password" id="password" placeholder="@Messages("user.password")" autocomplete="off" name="password"> |
|
24 |
- </label> |
|
25 |
- <label for="retypedPassword"> |
|
26 |
- <input type="password" class="text password" id="retypedPassword" placeholder="@Messages("validation.retypePassword")" autocomplete="off" name="retypedPassword"> |
|
27 |
- </label> |
|
13 |
+ <dl> |
|
14 |
+ <dt> |
|
15 |
+ <label for="loginId">@Messages("user.signupId")</label> |
|
16 |
+ </dt> |
|
17 |
+ <dd> |
|
18 |
+ <input type="text" class="text password" id="loginId" placeholder="" autocomplete="off" name="loginId"> |
|
19 |
+ </dd> |
|
20 |
+ |
|
21 |
+ <dt> |
|
22 |
+ <label for="uname">@Messages("user.name")</label> |
|
23 |
+ </dt> |
|
24 |
+ <dd> |
|
25 |
+ <input type="text" class="text password" id="uname" placeholder="" autocomplete="off" name="name"> |
|
26 |
+ </dd> |
|
27 |
+ |
|
28 |
+ <dt> |
|
29 |
+ <label for="email">@Messages("user.email")</label> |
|
30 |
+ </dt> |
|
31 |
+ <dd> |
|
32 |
+ <input type="text" class="text password" id="email" placeholder="" autocomplete="off" name="email"> |
|
33 |
+ </dd> |
|
34 |
+ |
|
35 |
+ <dt> |
|
36 |
+ <label for="password">@Messages("user.password")</label> |
|
37 |
+ </dt> |
|
38 |
+ <dd> |
|
39 |
+ <input type="password" class="text password" id="password" placeholder="" autocomplete="off" name="password"> |
|
40 |
+ </dd> |
|
41 |
+ |
|
42 |
+ <dt> |
|
43 |
+ <label for="retypedPassword">@Messages("validation.retypePassword")</label> |
|
44 |
+ </dt> |
|
45 |
+ <dd> |
|
46 |
+ <input type="password" class="text password" id="retypedPassword" placeholder="" autocomplete="off" name="retypedPassword"> |
|
47 |
+ </dd> |
|
48 |
+ </dl> |
|
49 |
+ |
|
28 | 50 |
<div class="act-row"> |
29 | 51 |
@Messages("user.isAlreadySignupUser") <a href="@routes.UserApp.loginForm()" class="go-login">@Messages("title.login")</a> |
30 | 52 |
</div> |
+++ public/javascripts/fileUploader.js
... | ... | @@ -0,0 +1,355 @@ |
1 | +/** | |
2 | + * getFileList | |
3 | + */ | |
4 | +var getFileList = function(target, urlToGetFileList, fn) { | |
5 | + var form = $('<form>').attr('method', 'get').attr('action', urlToGetFileList); | |
6 | + | |
7 | + var resourceType = target.attr('resourceType'); | |
8 | + if (typeof resourceType !== "undefined") { | |
9 | + form.append('<input type="hidden" name="containerType" value="' + resourceType + '">'); | |
10 | + } | |
11 | + | |
12 | + var resourceId = target.attr('resourceId'); | |
13 | + if (typeof resourceId !== "undefined") { | |
14 | + form.append('<input type="hidden" name="containerId" value="' + resourceId + '">'); | |
15 | + } | |
16 | + | |
17 | + form.ajaxForm({ | |
18 | + "success" : fn | |
19 | + }); | |
20 | + | |
21 | + try { | |
22 | + form.submit(); | |
23 | + } finally { | |
24 | + form = resourceType = resourceId = null; | |
25 | + } | |
26 | +}; | |
27 | + | |
28 | +/** | |
29 | + * fileUploader | |
30 | + */ | |
31 | +var fileUploader = (function() { | |
32 | + | |
33 | + var htVar = {}; | |
34 | + var htElements = {}; | |
35 | + | |
36 | + /** | |
37 | + * initialize fileUploader | |
38 | + * 파일 업로더 초기화 함수. fileUploader.init(htOptions) 으로 사용한다. | |
39 | + * @param {Hash Table} htOptions | |
40 | + * @param {Variant} htOptions.elTarget 첨부파일 | |
41 | + * @param {Variant} htOptions.elTextarea 이미지 첨부파일의 경우 클릭시 이 영역에 태그를 삽입한다 | |
42 | + * @param {String} htOptions.sTplFileItem 첨부한 파일명을 표시할 HTML 템플릿 | |
43 | + */ | |
44 | + function _init(htOptions){ | |
45 | + htOptions = htOptions || {}; | |
46 | + | |
47 | + _initVar(htOptions); | |
48 | + _initElement(htOptions); | |
49 | + _attachEvent(); | |
50 | + } | |
51 | + | |
52 | + /** | |
53 | + * init variables | |
54 | + */ | |
55 | + function _initVar(htOptions){ | |
56 | + htVar.nTotalSize = 0; | |
57 | + htVar.sAction = htOptions.sAction; | |
58 | + htVar.sTplFileItem = htOptions.sTplFileItem; | |
59 | + | |
60 | + htVar.htUploadOpts = { | |
61 | + "dataType" : "json", | |
62 | + "error" : _onErrorSubmitForm, | |
63 | + "success" : _onSuccessSubmitForm, | |
64 | + "beforeSubmit" : _onBeforeSubmitForm, | |
65 | + "uploadProgress": _onUploadProgressForm | |
66 | + }; | |
67 | + } | |
68 | + | |
69 | + /** | |
70 | + * init elements | |
71 | + */ | |
72 | + function _initElement(htOptions){ | |
73 | + htElements.welTarget = $(htOptions.elTarget); | |
74 | + htElements.welTextarea = $(htOptions.elTextarea); | |
75 | + | |
76 | + htElements.welInputFile = $(htOptions.elInputFile || ".file"); | |
77 | + htElements.welTotalNum = $(htOptions.elTotalNum || ".total-num"); | |
78 | + htElements.welProgressNum = $(htOptions.elProgressNum || ".progress-num"); | |
79 | + htElements.welProgressBar = $(htOptions.elProgressBar || ".progress > .bar"); | |
80 | + htElements.welFileList = $(htOptions.elFileList || "ul.attached-files"); | |
81 | + } | |
82 | + | |
83 | + /** | |
84 | + * init event handlers | |
85 | + */ | |
86 | + function _attachEvent(){ | |
87 | + htElements.welInputFile.change(_onChangeFile); | |
88 | + htElements.welInputFile.click(function(){ | |
89 | + _setProgressBar(0); | |
90 | + }); | |
91 | + } | |
92 | + | |
93 | + /** | |
94 | + * change event handler on <input type="file"> | |
95 | + */ | |
96 | + function _onChangeFile(){ | |
97 | + // Validation | |
98 | + var sFileName = _getBasename(htElements.welInputFile.val()); | |
99 | + //console.log("changeFile : " + sFileName); | |
100 | + | |
101 | + if(sFileName == ""){ | |
102 | + return; | |
103 | + } | |
104 | + | |
105 | + // Submit | |
106 | + var welForm = $('<form method="post" enctype="multipart/form-data" style="display:none">'); | |
107 | + var welInputFile = htElements.welInputFile.clone(); | |
108 | + welInputFile[0].files = htElements.welInputFile[0].files; | |
109 | + welForm.attr('action', htVar.sAction); | |
110 | + welForm.append(welInputFile).appendTo(document.body); | |
111 | + welForm.ajaxForm(htVar.htUploadOpts); | |
112 | + | |
113 | + try { | |
114 | + welForm.submit(); | |
115 | + } finally { | |
116 | + welInputFile.remove(); | |
117 | + welForm.remove(); | |
118 | + welForm = welInputFile = null; | |
119 | + } | |
120 | + } | |
121 | + | |
122 | + /** | |
123 | + * Returns trailing name component of path | |
124 | + * @param {String} sPath | |
125 | + * @returns {String} | |
126 | + */ | |
127 | + function _getBasename(sPath){ | |
128 | + var sSeparator = 'fakepath'; | |
129 | + var nPos = sPath.indexOf(sSeparator); | |
130 | + return (nPos > -1) ? sPath.substring(nPos + sSeparator.length + 1) : sPath; | |
131 | + } | |
132 | + | |
133 | + function _onBeforeSubmitForm(){ | |
134 | + var sFileName = _getBasename(htElements.welInputFile.val()); | |
135 | + //console.log("beforeSubmit: " + sFileName); | |
136 | + | |
137 | + return !(sFileName == ""); | |
138 | + } | |
139 | + | |
140 | + /** | |
141 | + * On success to submit temporary form created in onChangeFile() | |
142 | + * @param {Hash Table} htData | |
143 | + * @returns | |
144 | + */ | |
145 | + function _onSuccessSubmitForm(oRes){ | |
146 | + htElements.welInputFile.val(""); | |
147 | + | |
148 | + // Validation | |
149 | + if(!(oRes instanceof Object) || !oRes.name || !oRes.url){ | |
150 | + //console.log("Failed to upload - Server Error"); | |
151 | + _setProgressBar(0); | |
152 | + return; | |
153 | + } | |
154 | + | |
155 | + // create list item | |
156 | + var welItem = _createFileItem(oRes); | |
157 | + welItem.click(_onClickListItem); | |
158 | + htElements.welFileList.append(welItem); | |
159 | + | |
160 | + _setProgressBar(100); | |
161 | + _updateTotalFilesize(oRes.size); | |
162 | + } | |
163 | + | |
164 | + /** | |
165 | + * 첨부 파일 크기 합계 표시 | |
166 | + * @param {Number} nValue 첨부 파일 크기 변화할 값 | |
167 | + */ | |
168 | + function _updateTotalFilesize(nValue){ | |
169 | + nValue = (nValue || 0) * 1; | |
170 | + htVar.nTotalSize += nValue; | |
171 | + htElements.welTotalNum.text(humanize.filesize(htVar.nTotalSize)); | |
172 | + } | |
173 | + | |
174 | + /** | |
175 | + * Create uploaded file item HTML element using template string | |
176 | + * @param {Hash Table} htFile | |
177 | + * @returns {HTMLElement} | |
178 | + */ | |
179 | + function _createFileItem(htFile) { | |
180 | + var oItem = $.tmpl(htVar.sTplFileItem, { | |
181 | + "mimeType": htFile.mimeType, | |
182 | + "fileName": htFile.name, | |
183 | + "fileHref": htFile.url, | |
184 | + "fileSize": htFile.size, | |
185 | + "fileSizeReadable": humanize.filesize(htFile.size) | |
186 | + }); | |
187 | + | |
188 | + return oItem; | |
189 | + } | |
190 | + | |
191 | + /** | |
192 | + * On error to submit temporary form created in onChangeFile() | |
193 | + */ | |
194 | + function _onErrorSubmitForm(oRes){ | |
195 | + _setProgressBar(0); | |
196 | + //console.log("errorSubmit : %o", oRes); | |
197 | + } | |
198 | + | |
199 | + /** | |
200 | + * uploadProgress event handler | |
201 | + */ | |
202 | + function _onUploadProgressForm(oEvent, nPos, nTotal, nPercentComplete){ | |
203 | + _setProgressBar(nPercentComplete); | |
204 | + } | |
205 | + | |
206 | + /** | |
207 | + * Set Progress Bar Width | |
208 | + * @param nProgress | |
209 | + */ | |
210 | + function _setProgressBar(nProgress) { | |
211 | + nProgress = nProgress * 1; | |
212 | +// htElements.welTarget.css("opacity", (nProgress === 0) ? 0 : 1); | |
213 | + htElements.welProgressBar.css("width", nProgress + "%"); | |
214 | + htElements.welProgressNum.text(nProgress + "%"); | |
215 | + } | |
216 | + | |
217 | + | |
218 | + /** | |
219 | + * On Click attached files list | |
220 | + */ | |
221 | + function _onClickListItem(oEvt){ | |
222 | + var welTarget = $(oEvt.target); | |
223 | + var welItem = $(oEvt.currentTarget); | |
224 | + | |
225 | + // 파일 아이템 전체에 이벤트 핸들러가 설정되어 있으므로 | |
226 | + // 클릭이벤트 발생한 위치를 삭제버튼과 나머지 영역으로 구분하여 처리 | |
227 | + if(welTarget.hasClass("btn-delete")){ | |
228 | + _deleteAttachedFile(welItem); // 첨부파일 삭제 | |
229 | + } else { | |
230 | + _insertLinkToTextarea(welItem); // <textarea>에 링크 텍스트 추가 | |
231 | + } | |
232 | + } | |
233 | + | |
234 | + /** | |
235 | + * 선택한 파일 아이템의 링크 텍스트를 <textarea>에 추가하는 함수 | |
236 | + */ | |
237 | + function _insertLinkToTextarea(welItem){ | |
238 | + var welTextarea = htElements.welTextarea; | |
239 | + if(!welTextarea){ | |
240 | + return false; | |
241 | + } | |
242 | + | |
243 | + var nPos = welTextarea.prop('selectionStart'); | |
244 | + var sText = welTextarea.val(); | |
245 | + var sLink = _getLinkText(welItem); | |
246 | + | |
247 | + welTextarea.val(sText.substring(0, nPos) + sLink + sText.substring(nPos)); | |
248 | + } | |
249 | + | |
250 | + /** | |
251 | + * 선택한 파일 아이템을 첨부 파일에서 삭제 | |
252 | + * <textarea>에서 해당 파일의 링크 텍스트도 제거함 (_clearLinkInTextarea) | |
253 | + */ | |
254 | + function _deleteAttachedFile(welItem){ | |
255 | + /**/ | |
256 | + var nFileSize = welItem.attr("data-size") * 1; | |
257 | + var welForm = $('<form method="post" enctype="multipart/form-data" style="display:none">'); | |
258 | + welForm.attr('action', welItem.attr("data-href")); | |
259 | + welForm.append('<input type="hidden" name="_method" value="delete">'); | |
260 | + welForm.appendTo(document.body); | |
261 | + welForm.ajaxForm({ | |
262 | + "success" : function() { | |
263 | + _updateTotalFilesize(nFileSize * -1); | |
264 | + _clearLinkInTextarea(welItem); | |
265 | + _setProgressBar(0); | |
266 | + welItem.remove(); | |
267 | + } | |
268 | + }); | |
269 | + | |
270 | + try { | |
271 | + welForm.submit(); | |
272 | + } finally { | |
273 | + welForm.remove(); | |
274 | + welTextarea = welForm = null; | |
275 | + } | |
276 | + /**/ | |
277 | + | |
278 | + // TODO: 아래와 같은 간단한 AJAX 호출 사용 검토 | |
279 | + /* | |
280 | + var sActionURL = welItem.attr("data-href"); | |
281 | + $.post(sActionURL, {"_method": "delete"}, function(){ | |
282 | + welItem.remove(); | |
283 | + _clearLinkInTextarea(sLink); | |
284 | + _setProgressBar(0); | |
285 | + }); | |
286 | + */ | |
287 | + } | |
288 | + | |
289 | + /** | |
290 | + * 파일 아이템으로부터 링크 텍스트를 생성하여 반환하는 함수 | |
291 | + * @param {Wrapped Element} welItem 템플릿 htVar.sTplFileItem 에 의해 생성된 첨부 파일 아이템 | |
292 | + * @return {String} | |
293 | + */ | |
294 | + function _getLinkText(welItem){ | |
295 | + var sMimeType = welItem.attr("data-mime"); | |
296 | + var sFileName = welItem.attr("data-name"); | |
297 | + var sFilePath = welItem.attr("data-href"); | |
298 | + | |
299 | + var sLinkText = ''; | |
300 | + if (sMimeType.substr(0,5) == "image"){ | |
301 | + sLinkText = '<img src="' + sFilePath + '">'; | |
302 | + } else { | |
303 | + sLinkText = '[' + sFileName +'](' + sFilePath + ')'; | |
304 | + } | |
305 | + | |
306 | + return sLinkText; | |
307 | + } | |
308 | + | |
309 | + /** | |
310 | + * <textarea>에서 해당 파일 아이템의 링크 텍스트를 제거하는 함수 | |
311 | + * @param {Wrapped Element} welItem | |
312 | + */ | |
313 | + function _clearLinkInTextarea(welItem){ | |
314 | + var welTextarea = htElements.welTextarea; | |
315 | + if(!welTextarea){ | |
316 | + return false; | |
317 | + } | |
318 | + | |
319 | + var sLink = _getLinkText(welItem); | |
320 | + welTextarea.val(welTextarea.val().split(sLink).join('')); | |
321 | + } | |
322 | + | |
323 | + /** | |
324 | + * 인터페이스 반환 | |
325 | + */ | |
326 | + return { | |
327 | + "init": _init | |
328 | + }; | |
329 | +})(); | |
330 | + | |
331 | +/** | |
332 | + * fileDownloader | |
333 | + */ | |
334 | +var fileDownloader = function(target, urlToGetFileList) { | |
335 | + var createFileItem = function(file) { | |
336 | + var link = $('<a>').prop('href', file.url).append( | |
337 | + $('<i>').addClass('icon-download')).append( | |
338 | + $('<div>').text(file.name).html()); | |
339 | + | |
340 | + return $('<li>').append(link); | |
341 | + }; | |
342 | + | |
343 | + var filelist = $('<ul>'); | |
344 | + var addFiles = function(responseBody, statusText, xhr) { | |
345 | + var files = responseBody.attachments; | |
346 | + for ( var i = 0; i < files.length; i++) { | |
347 | + filelist.css('display', ''); | |
348 | + filelist.append(createFileItem(files[i])); | |
349 | + } | |
350 | + }; | |
351 | + | |
352 | + getFileList(target, urlToGetFileList, addFiles); | |
353 | + | |
354 | + target.append(filelist); | |
355 | +}; |
+++ public/javascripts/jquery.tmpl.js
... | ... | @@ -0,0 +1,484 @@ |
1 | +/*! | |
2 | + * jQuery Templates Plugin 1.0.0pre | |
3 | + * http://github.com/jquery/jquery-tmpl | |
4 | + * Requires jQuery 1.4.2 | |
5 | + * | |
6 | + * Copyright 2011, Software Freedom Conservancy, Inc. | |
7 | + * Dual licensed under the MIT or GPL Version 2 licenses. | |
8 | + * http://jquery.org/license | |
9 | + */ | |
10 | +(function( jQuery, undefined ){ | |
11 | + var oldManip = jQuery.fn.domManip, tmplItmAtt = "_tmplitem", htmlExpr = /^[^<]*(<[\w\W]+>)[^>]*$|\{\{\! /, | |
12 | + newTmplItems = {}, wrappedItems = {}, appendToTmplItems, topTmplItem = { key: 0, data: {} }, itemKey = 0, cloneIndex = 0, stack = []; | |
13 | + | |
14 | + function newTmplItem( options, parentItem, fn, data ) { | |
15 | + // Returns a template item data structure for a new rendered instance of a template (a 'template item'). | |
16 | + // The content field is a hierarchical array of strings and nested items (to be | |
17 | + // removed and replaced by nodes field of dom elements, once inserted in DOM). | |
18 | + var newItem = { | |
19 | + data: data || (data === 0 || data === false) ? data : (parentItem ? parentItem.data : {}), | |
20 | + _wrap: parentItem ? parentItem._wrap : null, | |
21 | + tmpl: null, | |
22 | + parent: parentItem || null, | |
23 | + nodes: [], | |
24 | + calls: tiCalls, | |
25 | + nest: tiNest, | |
26 | + wrap: tiWrap, | |
27 | + html: tiHtml, | |
28 | + update: tiUpdate | |
29 | + }; | |
30 | + if ( options ) { | |
31 | + jQuery.extend( newItem, options, { nodes: [], parent: parentItem }); | |
32 | + } | |
33 | + if ( fn ) { | |
34 | + // Build the hierarchical content to be used during insertion into DOM | |
35 | + newItem.tmpl = fn; | |
36 | + newItem._ctnt = newItem._ctnt || newItem.tmpl( jQuery, newItem ); | |
37 | + newItem.key = ++itemKey; | |
38 | + // Keep track of new template item, until it is stored as jQuery Data on DOM element | |
39 | + (stack.length ? wrappedItems : newTmplItems)[itemKey] = newItem; | |
40 | + } | |
41 | + return newItem; | |
42 | + } | |
43 | + | |
44 | + // Override appendTo etc., in order to provide support for targeting multiple elements. (This code would disappear if integrated in jquery core). | |
45 | + jQuery.each({ | |
46 | + appendTo: "append", | |
47 | + prependTo: "prepend", | |
48 | + insertBefore: "before", | |
49 | + insertAfter: "after", | |
50 | + replaceAll: "replaceWith" | |
51 | + }, function( name, original ) { | |
52 | + jQuery.fn[ name ] = function( selector ) { | |
53 | + var ret = [], insert = jQuery( selector ), elems, i, l, tmplItems, | |
54 | + parent = this.length === 1 && this[0].parentNode; | |
55 | + | |
56 | + appendToTmplItems = newTmplItems || {}; | |
57 | + if ( parent && parent.nodeType === 11 && parent.childNodes.length === 1 && insert.length === 1 ) { | |
58 | + insert[ original ]( this[0] ); | |
59 | + ret = this; | |
60 | + } else { | |
61 | + for ( i = 0, l = insert.length; i < l; i++ ) { | |
62 | + cloneIndex = i; | |
63 | + elems = (i > 0 ? this.clone(true) : this).get(); | |
64 | + jQuery( insert[i] )[ original ]( elems ); | |
65 | + ret = ret.concat( elems ); | |
66 | + } | |
67 | + cloneIndex = 0; | |
68 | + ret = this.pushStack( ret, name, insert.selector ); | |
69 | + } | |
70 | + tmplItems = appendToTmplItems; | |
71 | + appendToTmplItems = null; | |
72 | + jQuery.tmpl.complete( tmplItems ); | |
73 | + return ret; | |
74 | + }; | |
75 | + }); | |
76 | + | |
77 | + jQuery.fn.extend({ | |
78 | + // Use first wrapped element as template markup. | |
79 | + // Return wrapped set of template items, obtained by rendering template against data. | |
80 | + tmpl: function( data, options, parentItem ) { | |
81 | + return jQuery.tmpl( this[0], data, options, parentItem ); | |
82 | + }, | |
83 | + | |
84 | + // Find which rendered template item the first wrapped DOM element belongs to | |
85 | + tmplItem: function() { | |
86 | + return jQuery.tmplItem( this[0] ); | |
87 | + }, | |
88 | + | |
89 | + // Consider the first wrapped element as a template declaration, and get the compiled template or store it as a named template. | |
90 | + template: function( name ) { | |
91 | + return jQuery.template( name, this[0] ); | |
92 | + }, | |
93 | + | |
94 | + domManip: function( args, table, callback, options ) { | |
95 | + if ( args[0] && jQuery.isArray( args[0] )) { | |
96 | + var dmArgs = jQuery.makeArray( arguments ), elems = args[0], elemsLength = elems.length, i = 0, tmplItem; | |
97 | + while ( i < elemsLength && !(tmplItem = jQuery.data( elems[i++], "tmplItem" ))) {} | |
98 | + if ( tmplItem && cloneIndex ) { | |
99 | + dmArgs[2] = function( fragClone ) { | |
100 | + // Handler called by oldManip when rendered template has been inserted into DOM. | |
101 | + jQuery.tmpl.afterManip( this, fragClone, callback ); | |
102 | + }; | |
103 | + } | |
104 | + oldManip.apply( this, dmArgs ); | |
105 | + } else { | |
106 | + oldManip.apply( this, arguments ); | |
107 | + } | |
108 | + cloneIndex = 0; | |
109 | + if ( !appendToTmplItems ) { | |
110 | + jQuery.tmpl.complete( newTmplItems ); | |
111 | + } | |
112 | + return this; | |
113 | + } | |
114 | + }); | |
115 | + | |
116 | + jQuery.extend({ | |
117 | + // Return wrapped set of template items, obtained by rendering template against data. | |
118 | + tmpl: function( tmpl, data, options, parentItem ) { | |
119 | + var ret, topLevel = !parentItem; | |
120 | + if ( topLevel ) { | |
121 | + // This is a top-level tmpl call (not from a nested template using {{tmpl}}) | |
122 | + parentItem = topTmplItem; | |
123 | + tmpl = jQuery.template[tmpl] || jQuery.template( null, tmpl ); | |
124 | + wrappedItems = {}; // Any wrapped items will be rebuilt, since this is top level | |
125 | + } else if ( !tmpl ) { | |
126 | + // The template item is already associated with DOM - this is a refresh. | |
127 | + // Re-evaluate rendered template for the parentItem | |
128 | + tmpl = parentItem.tmpl; | |
129 | + newTmplItems[parentItem.key] = parentItem; | |
130 | + parentItem.nodes = []; | |
131 | + if ( parentItem.wrapped ) { | |
132 | + updateWrapped( parentItem, parentItem.wrapped ); | |
133 | + } | |
134 | + // Rebuild, without creating a new template item | |
135 | + return jQuery( build( parentItem, null, parentItem.tmpl( jQuery, parentItem ) )); | |
136 | + } | |
137 | + if ( !tmpl ) { | |
138 | + return []; // Could throw... | |
139 | + } | |
140 | + if ( typeof data === "function" ) { | |
141 | + data = data.call( parentItem || {} ); | |
142 | + } | |
143 | + if ( options && options.wrapped ) { | |
144 | + updateWrapped( options, options.wrapped ); | |
145 | + } | |
146 | + ret = jQuery.isArray( data ) ? | |
147 | + jQuery.map( data, function( dataItem ) { | |
148 | + return dataItem ? newTmplItem( options, parentItem, tmpl, dataItem ) : null; | |
149 | + }) : | |
150 | + [ newTmplItem( options, parentItem, tmpl, data ) ]; | |
151 | + return topLevel ? jQuery( build( parentItem, null, ret ) ) : ret; | |
152 | + }, | |
153 | + | |
154 | + // Return rendered template item for an element. | |
155 | + tmplItem: function( elem ) { | |
156 | + var tmplItem; | |
157 | + if ( elem instanceof jQuery ) { | |
158 | + elem = elem[0]; | |
159 | + } | |
160 | + while ( elem && elem.nodeType === 1 && !(tmplItem = jQuery.data( elem, "tmplItem" )) && (elem = elem.parentNode) ) {} | |
161 | + return tmplItem || topTmplItem; | |
162 | + }, | |
163 | + | |
164 | + // Set: | |
165 | + // Use $.template( name, tmpl ) to cache a named template, | |
166 | + // where tmpl is a template string, a script element or a jQuery instance wrapping a script element, etc. | |
167 | + // Use $( "selector" ).template( name ) to provide access by name to a script block template declaration. | |
168 | + | |
169 | + // Get: | |
170 | + // Use $.template( name ) to access a cached template. | |
171 | + // Also $( selectorToScriptBlock ).template(), or $.template( null, templateString ) | |
172 | + // will return the compiled template, without adding a name reference. | |
173 | + // If templateString includes at least one HTML tag, $.template( templateString ) is equivalent | |
174 | + // to $.template( null, templateString ) | |
175 | + template: function( name, tmpl ) { | |
176 | + if (tmpl) { | |
177 | + // Compile template and associate with name | |
178 | + if ( typeof tmpl === "string" ) { | |
179 | + // This is an HTML string being passed directly in. | |
180 | + tmpl = buildTmplFn( tmpl ); | |
181 | + } else if ( tmpl instanceof jQuery ) { | |
182 | + tmpl = tmpl[0] || {}; | |
183 | + } | |
184 | + if ( tmpl.nodeType ) { | |
185 | + // If this is a template block, use cached copy, or generate tmpl function and cache. | |
186 | + tmpl = jQuery.data( tmpl, "tmpl" ) || jQuery.data( tmpl, "tmpl", buildTmplFn( tmpl.innerHTML )); | |
187 | + // Issue: In IE, if the container element is not a script block, the innerHTML will remove quotes from attribute values whenever the value does not include white space. | |
188 | + // This means that foo="${x}" will not work if the value of x includes white space: foo="${x}" -> foo=value of x. | |
189 | + // To correct this, include space in tag: foo="${ x }" -> foo="value of x" | |
190 | + } | |
191 | + return typeof name === "string" ? (jQuery.template[name] = tmpl) : tmpl; | |
192 | + } | |
193 | + // Return named compiled template | |
194 | + return name ? (typeof name !== "string" ? jQuery.template( null, name ): | |
195 | + (jQuery.template[name] || | |
196 | + // If not in map, and not containing at least on HTML tag, treat as a selector. | |
197 | + // (If integrated with core, use quickExpr.exec) | |
198 | + jQuery.template( null, htmlExpr.test( name ) ? name : jQuery( name )))) : null; | |
199 | + }, | |
200 | + | |
201 | + encode: function( text ) { | |
202 | + // Do HTML encoding replacing < > & and ' and " by corresponding entities. | |
203 | + return ("" + text).split("<").join("<").split(">").join(">").split('"').join(""").split("'").join("'"); | |
204 | + } | |
205 | + }); | |
206 | + | |
207 | + jQuery.extend( jQuery.tmpl, { | |
208 | + tag: { | |
209 | + "tmpl": { | |
210 | + _default: { $2: "null" }, | |
211 | + open: "if($notnull_1){__=__.concat($item.nest($1,$2));}" | |
212 | + // tmpl target parameter can be of type function, so use $1, not $1a (so not auto detection of functions) | |
213 | + // This means that {{tmpl foo}} treats foo as a template (which IS a function). | |
214 | + // Explicit parens can be used if foo is a function that returns a template: {{tmpl foo()}}. | |
215 | + }, | |
216 | + "wrap": { | |
217 | + _default: { $2: "null" }, | |
218 | + open: "$item.calls(__,$1,$2);__=[];", | |
219 | + close: "call=$item.calls();__=call._.concat($item.wrap(call,__));" | |
220 | + }, | |
221 | + "each": { | |
222 | + _default: { $2: "$index, $value" }, | |
223 | + open: "if($notnull_1){$.each($1a,function($2){with(this){", | |
224 | + close: "}});}" | |
225 | + }, | |
226 | + "if": { | |
227 | + open: "if(($notnull_1) && $1a){", | |
228 | + close: "}" | |
229 | + }, | |
230 | + "else": { | |
231 | + _default: { $1: "true" }, | |
232 | + open: "}else if(($notnull_1) && $1a){" | |
233 | + }, | |
234 | + "html": { | |
235 | + // Unecoded expression evaluation. | |
236 | + open: "if($notnull_1){__.push($1a);}" | |
237 | + }, | |
238 | + "=": { | |
239 | + // Encoded expression evaluation. Abbreviated form is ${}. | |
240 | + _default: { $1: "$data" }, | |
241 | + open: "if($notnull_1){__.push($.encode($1a));}" | |
242 | + }, | |
243 | + "!": { | |
244 | + // Comment tag. Skipped by parser | |
245 | + open: "" | |
246 | + } | |
247 | + }, | |
248 | + | |
249 | + // This stub can be overridden, e.g. in jquery.tmplPlus for providing rendered events | |
250 | + complete: function( items ) { | |
251 | + newTmplItems = {}; | |
252 | + }, | |
253 | + | |
254 | + // Call this from code which overrides domManip, or equivalent | |
255 | + // Manage cloning/storing template items etc. | |
256 | + afterManip: function afterManip( elem, fragClone, callback ) { | |
257 | + // Provides cloned fragment ready for fixup prior to and after insertion into DOM | |
258 | + var content = fragClone.nodeType === 11 ? | |
259 | + jQuery.makeArray(fragClone.childNodes) : | |
260 | + fragClone.nodeType === 1 ? [fragClone] : []; | |
261 | + | |
262 | + // Return fragment to original caller (e.g. append) for DOM insertion | |
263 | + callback.call( elem, fragClone ); | |
264 | + | |
265 | + // Fragment has been inserted:- Add inserted nodes to tmplItem data structure. Replace inserted element annotations by jQuery.data. | |
266 | + storeTmplItems( content ); | |
267 | + cloneIndex++; | |
268 | + } | |
269 | + }); | |
270 | + | |
271 | + //========================== Private helper functions, used by code above ========================== | |
272 | + | |
273 | + function build( tmplItem, nested, content ) { | |
274 | + // Convert hierarchical content into flat string array | |
275 | + // and finally return array of fragments ready for DOM insertion | |
276 | + var frag, ret = content ? jQuery.map( content, function( item ) { | |
277 | + return (typeof item === "string") ? | |
278 | + // Insert template item annotations, to be converted to jQuery.data( "tmplItem" ) when elems are inserted into DOM. | |
279 | + (tmplItem.key ? item.replace( /(<\w+)(?=[\s>])(?![^>]*_tmplitem)([^>]*)/g, "$1 " + tmplItmAtt + "=\"" + tmplItem.key + "\" $2" ) : item) : | |
280 | + // This is a child template item. Build nested template. | |
281 | + build( item, tmplItem, item._ctnt ); | |
282 | + }) : | |
283 | + // If content is not defined, insert tmplItem directly. Not a template item. May be a string, or a string array, e.g. from {{html $item.html()}}. | |
284 | + tmplItem; | |
285 | + if ( nested ) { | |
286 | + return ret; | |
287 | + } | |
288 | + | |
289 | + // top-level template | |
290 | + ret = ret.join(""); | |
291 | + | |
292 | + // Support templates which have initial or final text nodes, or consist only of text | |
293 | + // Also support HTML entities within the HTML markup. | |
294 | + ret.replace( /^\s*([^<\s][^<]*)?(<[\w\W]+>)([^>]*[^>\s])?\s*$/, function( all, before, middle, after) { | |
295 | + frag = jQuery( middle ).get(); | |
296 | + | |
297 | + storeTmplItems( frag ); | |
298 | + if ( before ) { | |
299 | + frag = unencode( before ).concat(frag); | |
300 | + } | |
301 | + if ( after ) { | |
302 | + frag = frag.concat(unencode( after )); | |
303 | + } | |
304 | + }); | |
305 | + return frag ? frag : unencode( ret ); | |
306 | + } | |
307 | + | |
308 | + function unencode( text ) { | |
309 | + // Use createElement, since createTextNode will not render HTML entities correctly | |
310 | + var el = document.createElement( "div" ); | |
311 | + el.innerHTML = text; | |
312 | + return jQuery.makeArray(el.childNodes); | |
313 | + } | |
314 | + | |
315 | + // Generate a reusable function that will serve to render a template against data | |
316 | + function buildTmplFn( markup ) { | |
317 | + return new Function("jQuery","$item", | |
318 | + // Use the variable __ to hold a string array while building the compiled template. (See https://github.com/jquery/jquery-tmpl/issues#issue/10). | |
319 | + "var $=jQuery,call,__=[],$data=$item.data;" + | |
320 | + | |
321 | + // Introduce the data as local variables using with(){} | |
322 | + "with($data){__.push('" + | |
323 | + | |
324 | + // Convert the template into pure JavaScript | |
325 | + jQuery.trim(markup) | |
326 | + .replace( /([\\'])/g, "\\$1" ) | |
327 | + .replace( /[\r\t\n]/g, " " ) | |
328 | + .replace( /\$\{([^\}]*)\}/g, "{{= $1}}" ) | |
329 | + .replace( /\{\{(\/?)(\w+|.)(?:\(((?:[^\}]|\}(?!\}))*?)?\))?(?:\s+(.*?)?)?(\(((?:[^\}]|\}(?!\}))*?)\))?\s*\}\}/g, | |
330 | + function( all, slash, type, fnargs, target, parens, args ) { | |
331 | + var tag = jQuery.tmpl.tag[ type ], def, expr, exprAutoFnDetect; | |
332 | + if ( !tag ) { | |
333 | + throw "Unknown template tag: " + type; | |
334 | + } | |
335 | + def = tag._default || []; | |
336 | + if ( parens && !/\w$/.test(target)) { | |
337 | + target += parens; | |
338 | + parens = ""; | |
339 | + } | |
340 | + if ( target ) { | |
341 | + target = unescape( target ); | |
342 | + args = args ? ("," + unescape( args ) + ")") : (parens ? ")" : ""); | |
343 | + // Support for target being things like a.toLowerCase(); | |
344 | + // In that case don't call with template item as 'this' pointer. Just evaluate... | |
345 | + expr = parens ? (target.indexOf(".") > -1 ? target + unescape( parens ) : ("(" + target + ").call($item" + args)) : target; | |
346 | + exprAutoFnDetect = parens ? expr : "(typeof(" + target + ")==='function'?(" + target + ").call($item):(" + target + "))"; | |
347 | + } else { | |
348 | + exprAutoFnDetect = expr = def.$1 || "null"; | |
349 | + } | |
350 | + fnargs = unescape( fnargs ); | |
351 | + return "');" + | |
352 | + tag[ slash ? "close" : "open" ] | |
353 | + .split( "$notnull_1" ).join( target ? "typeof(" + target + ")!=='undefined' && (" + target + ")!=null" : "true" ) | |
354 | + .split( "$1a" ).join( exprAutoFnDetect ) | |
355 | + .split( "$1" ).join( expr ) | |
356 | + .split( "$2" ).join( fnargs || def.$2 || "" ) + | |
357 | + "__.push('"; | |
358 | + }) + | |
359 | + "');}return __;" | |
360 | + ); | |
361 | + } | |
362 | + function updateWrapped( options, wrapped ) { | |
363 | + // Build the wrapped content. | |
364 | + options._wrap = build( options, true, | |
365 | + // Suport imperative scenario in which options.wrapped can be set to a selector or an HTML string. | |
366 | + jQuery.isArray( wrapped ) ? wrapped : [htmlExpr.test( wrapped ) ? wrapped : jQuery( wrapped ).html()] | |
367 | + ).join(""); | |
368 | + } | |
369 | + | |
370 | + function unescape( args ) { | |
371 | + return args ? args.replace( /\\'/g, "'").replace(/\\\\/g, "\\" ) : null; | |
372 | + } | |
373 | + function outerHtml( elem ) { | |
374 | + var div = document.createElement("div"); | |
375 | + div.appendChild( elem.cloneNode(true) ); | |
376 | + return div.innerHTML; | |
377 | + } | |
378 | + | |
379 | + // Store template items in jQuery.data(), ensuring a unique tmplItem data data structure for each rendered template instance. | |
380 | + function storeTmplItems( content ) { | |
381 | + var keySuffix = "_" + cloneIndex, elem, elems, newClonedItems = {}, i, l, m; | |
382 | + for ( i = 0, l = content.length; i < l; i++ ) { | |
383 | + if ( (elem = content[i]).nodeType !== 1 ) { | |
384 | + continue; | |
385 | + } | |
386 | + elems = elem.getElementsByTagName("*"); | |
387 | + for ( m = elems.length - 1; m >= 0; m-- ) { | |
388 | + processItemKey( elems[m] ); | |
389 | + } | |
390 | + processItemKey( elem ); | |
391 | + } | |
392 | + function processItemKey( el ) { | |
393 | + var pntKey, pntNode = el, pntItem, tmplItem, key; | |
394 | + // Ensure that each rendered template inserted into the DOM has its own template item, | |
395 | + if ( (key = el.getAttribute( tmplItmAtt ))) { | |
396 | + while ( pntNode.parentNode && (pntNode = pntNode.parentNode).nodeType === 1 && !(pntKey = pntNode.getAttribute( tmplItmAtt ))) { } | |
397 | + if ( pntKey !== key ) { | |
398 | + // The next ancestor with a _tmplitem expando is on a different key than this one. | |
399 | + // So this is a top-level element within this template item | |
400 | + // Set pntNode to the key of the parentNode, or to 0 if pntNode.parentNode is null, or pntNode is a fragment. | |
401 | + pntNode = pntNode.parentNode ? (pntNode.nodeType === 11 ? 0 : (pntNode.getAttribute( tmplItmAtt ) || 0)) : 0; | |
402 | + if ( !(tmplItem = newTmplItems[key]) ) { | |
403 | + // The item is for wrapped content, and was copied from the temporary parent wrappedItem. | |
404 | + tmplItem = wrappedItems[key]; | |
405 | + tmplItem = newTmplItem( tmplItem, newTmplItems[pntNode]||wrappedItems[pntNode] ); | |
406 | + tmplItem.key = ++itemKey; | |
407 | + newTmplItems[itemKey] = tmplItem; | |
408 | + } | |
409 | + if ( cloneIndex ) { | |
410 | + cloneTmplItem( key ); | |
411 | + } | |
412 | + } | |
413 | + el.removeAttribute( tmplItmAtt ); | |
414 | + } else if ( cloneIndex && (tmplItem = jQuery.data( el, "tmplItem" )) ) { | |
415 | + // This was a rendered element, cloned during append or appendTo etc. | |
416 | + // TmplItem stored in jQuery data has already been cloned in cloneCopyEvent. We must replace it with a fresh cloned tmplItem. | |
417 | + cloneTmplItem( tmplItem.key ); | |
418 | + newTmplItems[tmplItem.key] = tmplItem; | |
419 | + pntNode = jQuery.data( el.parentNode, "tmplItem" ); | |
420 | + pntNode = pntNode ? pntNode.key : 0; | |
421 | + } | |
422 | + if ( tmplItem ) { | |
423 | + pntItem = tmplItem; | |
424 | + // Find the template item of the parent element. | |
425 | + // (Using !=, not !==, since pntItem.key is number, and pntNode may be a string) | |
426 | + while ( pntItem && pntItem.key != pntNode ) { | |
427 | + // Add this element as a top-level node for this rendered template item, as well as for any | |
428 | + // ancestor items between this item and the item of its parent element | |
429 | + pntItem.nodes.push( el ); | |
430 | + pntItem = pntItem.parent; | |
431 | + } | |
432 | + // Delete content built during rendering - reduce API surface area and memory use, and avoid exposing of stale data after rendering... | |
433 | + delete tmplItem._ctnt; | |
434 | + delete tmplItem._wrap; | |
435 | + // Store template item as jQuery data on the element | |
436 | + jQuery.data( el, "tmplItem", tmplItem ); | |
437 | + } | |
438 | + function cloneTmplItem( key ) { | |
439 | + key = key + keySuffix; | |
440 | + tmplItem = newClonedItems[key] = | |
441 | + (newClonedItems[key] || newTmplItem( tmplItem, newTmplItems[tmplItem.parent.key + keySuffix] || tmplItem.parent )); | |
442 | + } | |
443 | + } | |
444 | + } | |
445 | + | |
446 | + //---- Helper functions for template item ---- | |
447 | + | |
448 | + function tiCalls( content, tmpl, data, options ) { | |
449 | + if ( !content ) { | |
450 | + return stack.pop(); | |
451 | + } | |
452 | + stack.push({ _: content, tmpl: tmpl, item:this, data: data, options: options }); | |
453 | + } | |
454 | + | |
455 | + function tiNest( tmpl, data, options ) { | |
456 | + // nested template, using {{tmpl}} tag | |
457 | + return jQuery.tmpl( jQuery.template( tmpl ), data, options, this ); | |
458 | + } | |
459 | + | |
460 | + function tiWrap( call, wrapped ) { | |
461 | + // nested template, using {{wrap}} tag | |
462 | + var options = call.options || {}; | |
463 | + options.wrapped = wrapped; | |
464 | + // Apply the template, which may incorporate wrapped content, | |
465 | + return jQuery.tmpl( jQuery.template( call.tmpl ), call.data, options, call.item ); | |
466 | + } | |
467 | + | |
468 | + function tiHtml( filter, textOnly ) { | |
469 | + var wrapped = this._wrap; | |
470 | + return jQuery.map( | |
471 | + jQuery( jQuery.isArray( wrapped ) ? wrapped.join("") : wrapped ).filter( filter || "*" ), | |
472 | + function(e) { | |
473 | + return textOnly ? | |
474 | + e.innerText || e.textContent : | |
475 | + e.outerHTML || outerHtml(e); | |
476 | + }); | |
477 | + } | |
478 | + | |
479 | + function tiUpdate() { | |
480 | + var coll = this.nodes; | |
481 | + jQuery.tmpl( null, null, null, this).insertBefore( coll[0] ); | |
482 | + jQuery( coll ).remove(); | |
483 | + } | |
484 | +})( jQuery ); |
--- public/javascripts/modules/board.js
+++ public/javascripts/modules/board.js
... | ... | @@ -1,19 +1,27 @@ |
1 |
+/** |
|
2 |
+ * nforge.board.js |
|
3 |
+ */ |
|
1 | 4 |
nforge.namespace("board"); |
5 |
+ |
|
6 |
+/** |
|
7 |
+ * PostList |
|
8 |
+ */ |
|
2 | 9 |
|
3 | 10 |
nforge.board.list = function() { |
4 | 11 |
var that = { |
5 |
- init : function() { |
|
12 |
+ "init" : function() { |
|
6 | 13 |
that.setUpEventListener(); |
7 | 14 |
}, |
8 | 15 |
|
9 |
- setUpEventListener : function() { |
|
16 |
+ "setUpEventListener" : function() { |
|
10 | 17 |
var $headers = $("#order a"); |
11 | 18 |
$headers.click(that.onHeader); |
19 |
+ |
|
12 | 20 |
var $pagination = $("#pagination a"); |
13 | 21 |
$pagination.click(that.onPager); |
14 | 22 |
}, |
15 | 23 |
|
16 |
- onHeader : function() { |
|
24 |
+ "onHeader" : function() { |
|
17 | 25 |
var key = $(this).attr("key"); |
18 | 26 |
var $input = $("#option_form input[name=key]"); |
19 | 27 |
if (key !== $input.val()) { |
... | ... | @@ -21,28 +29,29 @@ |
21 | 29 |
} else { |
22 | 30 |
$input = $("#option_form input[name=order]"); |
23 | 31 |
if ($input.val() === "desc"){ |
24 |
- $input.val("asc"); |
|
25 |
- } |
|
26 |
- else if ($input.val() === "asc") { |
|
27 |
- $input.val("desc"); |
|
32 |
+ $input.val("asc"); |
|
33 |
+ } else if ($input.val() === "asc") { |
|
34 |
+ $input.val("desc"); |
|
28 | 35 |
} |
29 | 36 |
} |
30 | 37 |
$("#option_form").submit(); |
31 | 38 |
return false; |
32 | 39 |
}, |
33 | 40 |
|
34 |
- onPager : function() { |
|
41 |
+ "onPager" : function() { |
|
35 | 42 |
var $input = $("#option_form input[name=pageNum]"); |
36 | 43 |
$input.val($(this).attr("pageNum")); |
37 | 44 |
$("#option_form").submit(); |
38 | 45 |
return false; |
39 | 46 |
} |
40 | 47 |
}; |
48 |
+ |
|
41 | 49 |
return that; |
42 | 50 |
}; |
51 |
+ |
|
43 | 52 |
nforge.board.vaildate = function() { |
44 | 53 |
var that = { |
45 |
- init : function() { |
|
54 |
+ "init" : function() { |
|
46 | 55 |
$("form").submit(function() { |
47 | 56 |
if ($("input#title").val() === "" || $("textarea#contents").val() === "") { |
48 | 57 |
$("#warning button").click(function(){ |
... | ... | @@ -50,23 +59,31 @@ |
50 | 59 |
}); |
51 | 60 |
$('#warning').show(); |
52 | 61 |
return false; |
53 |
- } else { |
|
54 |
- return true; |
|
55 | 62 |
} |
63 |
+ return true; |
|
56 | 64 |
}); |
57 | 65 |
} |
58 | 66 |
}; |
59 | 67 |
return that; |
60 | 68 |
}; |
61 | 69 |
|
70 |
+/** |
|
71 |
+ * PostView |
|
72 |
+ */ |
|
62 | 73 |
nforge.board.view = function() { |
63 | 74 |
var that = { |
64 |
- init : function(filesUrl) { |
|
75 |
+ "init" : function(filesUrl) { |
|
65 | 76 |
var attachments; |
66 | 77 |
|
67 |
- fileUploader($('#upload'), $('#contents'), filesUrl); |
|
78 |
+ fileUploader.init({ |
|
79 |
+ "elTarget" : $('#upload'), // upload area |
|
80 |
+ "elTextarea" : $('#contents'), // textarea |
|
81 |
+ "sTplFileItem": $('#tplAttachedFile').text(), |
|
82 |
+ "sAction" : filesUrl |
|
83 |
+ }); |
|
84 |
+ |
|
68 | 85 |
attachments = $('.attachments'); |
69 |
- for (var i = 0; i < attachments.length; i++) { |
|
86 |
+ for (var i = 0, nLength = attachments.length; i < nLength; i++) { |
|
70 | 87 |
fileDownloader($(attachments[i]), filesUrl); |
71 | 88 |
} |
72 | 89 |
} |
... | ... | @@ -75,16 +92,24 @@ |
75 | 92 |
return that; |
76 | 93 |
}; |
77 | 94 |
|
95 |
+/** |
|
96 |
+ * newPost |
|
97 |
+ */ |
|
78 | 98 |
nforge.board.new = function() { |
79 |
- var that; |
|
99 |
+ var that = { |
|
100 |
+ "init": function(filesUrl) { |
|
101 |
+ fileUploader.init({ |
|
102 |
+ "elTarget" : $('#upload'), // upload area |
|
103 |
+ "elTextarea" : $('#contents'), // textarea |
|
104 |
+ "sTplFileItem": $('#tplAttachedFile').text(), |
|
105 |
+ "sAction" : filesUrl |
|
106 |
+ }); |
|
107 |
+ } |
|
108 |
+ }; |
|
80 | 109 |
|
81 |
- that = { |
|
82 |
- init: function(filesUrl) { |
|
83 |
- fileUploader($('#upload'), $('#contents'), filesUrl); |
|
84 |
- } |
|
85 |
- } |
|
86 |
- |
|
87 |
- return that; |
|
110 |
+ return that; |
|
88 | 111 |
}; |
89 | 112 |
|
113 |
+ |
|
114 |
+// Alias |
|
90 | 115 |
nforge.board.edit = nforge.issue.new; |
--- public/javascripts/modules/markdown.js
+++ public/javascripts/modules/markdown.js
... | ... | @@ -1,63 +1,78 @@ |
1 |
+/** |
|
2 |
+ * nforge.markdown.js |
|
3 |
+ */ |
|
4 |
+ |
|
1 | 5 |
nforge.namespace('markdown'); |
2 | 6 |
|
3 |
-var renderMarkdown = function(text) { |
|
4 |
- text = text |
|
5 |
- .replace(/```(\w+)(?:\r\n|\r|\n)((\r|\n|.)*?)(\r|\n)```/gm, function(match, p1, p2) { |
|
6 |
- try { |
|
7 |
- return '<pre><code class="' + p1 + '">' + hljs(p2, p1).value + '</code></pre>'; |
|
8 |
- } catch (e) { |
|
9 |
- return '<pre><code>' + hljs(p2).value + '</code></pre>'; |
|
10 |
- } |
|
11 |
- }); |
|
7 |
+(function(){ |
|
8 |
+ var rxMarkdown = /```(\w+)(?:\r\n|\r|\n)((\r|\n|.)*?)(\r|\n)```/gm; |
|
12 | 9 |
|
13 |
- return new Showdown.converter().makeHtml(text); |
|
14 |
-}; |
|
10 |
+ var renderMarkdown = function(text) { |
|
11 |
+ text = text.replace(rxMarkdown, function(match, p1, p2) { |
|
12 |
+ try { |
|
13 |
+ return '<pre><code class="' + p1 + '">' + hljs(p2, p1).value + '</code></pre>'; |
|
14 |
+ } catch (e) { |
|
15 |
+ return '<pre><code>' + hljs(p2).value + '</code></pre>'; |
|
16 |
+ } |
|
17 |
+ }); |
|
15 | 18 |
|
16 |
-var editor = function (textarea) { |
|
17 |
- var previewDiv, previewSwitch; |
|
19 |
+ return new Showdown.converter().makeHtml(text); |
|
20 |
+ }; |
|
18 | 21 |
|
19 |
- previewDiv = $('<div>'); |
|
20 |
- previewDiv.attr('div', 'preview'); |
|
21 |
- previewDiv.css('display', 'none'); |
|
22 |
+ var sPreviewSwitchHTML = '\ |
|
23 |
+<input type="radio" name="edit-mode" id="edit-mode" value="edit" checked="checked" class="radio-btn" />\ |
|
24 |
+<label for="edit-mode" style="margin-right:3px;">Edit</label>\ |
|
25 |
+<input type="radio" name="edit-mode" id="preview-mode" value="preview" class="radio-btn" />\ |
|
26 |
+<label for="preview-mode">Preview</label>'; |
|
22 | 27 |
|
23 |
- previewSwitch = $('<div id="mode-select">'); |
|
24 |
- previewSwitch.append($('<input type="radio" name="edit-mode" id="edit-mode" value="edit" checked>Edit</input>')); |
|
25 |
- previewSwitch.append($('<input type="radio" name="edit-mode" id="preview-mode" value="preview">Preview</input>')); |
|
26 |
- previewSwitch.change(function() { |
|
27 |
- var val = $('input:radio[name=edit-mode]:checked').val(); |
|
28 |
- if (val == 'preview') { |
|
29 |
- previewDiv.html(renderMarkdown(textarea.val())); |
|
30 |
- textarea.css('display', 'none'); |
|
31 |
- previewDiv.css('display', ''); |
|
32 |
- } else { |
|
33 |
- textarea.css('display', ''); |
|
34 |
- previewDiv.css('display', 'none'); |
|
35 |
- } |
|
36 |
- }); |
|
28 |
+ var editor = function (textarea) { |
|
29 |
+ var previewDiv, previewSwitch; |
|
37 | 30 |
|
38 |
- textarea.before(previewSwitch); |
|
39 |
- textarea.before(previewDiv); |
|
40 |
-}; |
|
31 |
+ previewDiv = $('<div class="markdown-preview">'); |
|
32 |
+ previewDiv.css('display', 'none'); |
|
33 |
+ previewDiv.css('width', (textarea.width()) + 'px'); |
|
34 |
+ previewDiv.css('min-height', textarea.height() + 'px'); |
|
35 |
+ previewDiv.css('padding', textarea.css("padding")); |
|
41 | 36 |
|
42 |
-var viewer = function (target) { |
|
43 |
- target.html(renderMarkdown(target.text())); |
|
44 |
-}; |
|
37 |
+ previewSwitch = $('<div id="mode-select">'); |
|
38 |
+ previewSwitch.append($(sPreviewSwitchHTML)); |
|
45 | 39 |
|
46 |
-nforge.markdown.enable = function() { |
|
47 |
- var that = { |
|
48 |
- init: function(targets) { |
|
49 |
- for(var i = 0; i < targets.length; i++) { |
|
50 |
- var target = targets[i]; |
|
51 |
- var tagname = target.tagName.toLowerCase(); |
|
52 |
- if (tagname == 'textarea' || tagname == 'input' |
|
53 |
- || target.contentEditable == 'true') { |
|
54 |
- editor($(target)); |
|
55 |
- } else { |
|
56 |
- viewer($(target)); |
|
57 |
- } |
|
58 |
- } |
|
59 |
- } |
|
60 |
- }; |
|
40 |
+ previewSwitch.change(function() { |
|
41 |
+ var val = $('input:radio[name=edit-mode]:checked').val(); |
|
42 |
+ if (val == 'preview') { |
|
43 |
+ previewDiv.html(renderMarkdown(textarea.val())); |
|
44 |
+ textarea.css('display', 'none'); |
|
45 |
+ previewDiv.css('display', ''); |
|
46 |
+ } else { |
|
47 |
+ textarea.css('display', ''); |
|
48 |
+ previewDiv.css('display', 'none'); |
|
49 |
+ } |
|
50 |
+ }); |
|
61 | 51 |
|
62 |
- return that; |
|
63 |
-} |
|
52 |
+ textarea.before(previewSwitch); |
|
53 |
+ textarea.before(previewDiv); |
|
54 |
+ }; |
|
55 |
+ |
|
56 |
+ var viewer = function (target) { |
|
57 |
+ target.html(renderMarkdown(target.text())); |
|
58 |
+ }; |
|
59 |
+ |
|
60 |
+ nforge.markdown.enable = function() { |
|
61 |
+ var that = { |
|
62 |
+ "init" : function(targets) { |
|
63 |
+ var nLength = targets.length; |
|
64 |
+ for ( var i = 0; i < nLength; i++) { |
|
65 |
+ var target = targets[i]; |
|
66 |
+ var tagname = target.tagName.toLowerCase(); |
|
67 |
+ if (tagname == 'textarea' || tagname == 'input' || target.contentEditable == 'true') { |
|
68 |
+ editor($(target)); |
|
69 |
+ } else { |
|
70 |
+ viewer($(target)); |
|
71 |
+ } |
|
72 |
+ } |
|
73 |
+ } |
|
74 |
+ }; |
|
75 |
+ |
|
76 |
+ return that; |
|
77 |
+ }; |
|
78 |
+})(); |
--- public/javascripts/uploader.js
+++ public/javascripts/uploader.js
... | ... | @@ -1,262 +1,270 @@ |
1 |
+/** |
|
2 |
+ * getFileList |
|
3 |
+ */ |
|
1 | 4 |
var getFileList = function(target, urlToGetFileList, fn) { |
2 |
- var form, resourceType, resourceId; |
|
5 |
+ var form = $('<form>').attr('method', 'get').attr('action', urlToGetFileList); |
|
3 | 6 |
|
4 |
- form = $('<form>') |
|
5 |
- .attr('method', 'get') |
|
6 |
- .attr('action', urlToGetFileList); |
|
7 |
+ var resourceType = target.attr('resourceType'); |
|
8 |
+ if (typeof resourceType !== "undefined") { |
|
9 |
+ form.append('<input type="hidden" name="containerType" value="' + resourceType + '">'); |
|
10 |
+ } |
|
7 | 11 |
|
8 |
- resourceType = target.attr('resourceType'); |
|
9 |
- if (resourceType !== undefined) { |
|
10 |
- form.append('<input type="hidden" name="containerType" value="' + resourceType + '">'); |
|
11 |
- } |
|
12 |
+ var resourceId = target.attr('resourceId'); |
|
13 |
+ if (typeof resourceId !== "undefined") { |
|
14 |
+ form.append('<input type="hidden" name="containerId" value="' + resourceId + '">'); |
|
15 |
+ } |
|
12 | 16 |
|
13 |
- resourceId = target.attr('resourceId'); |
|
14 |
- if (resourceId !== undefined) { |
|
15 |
- form.append('<input type="hidden" name="containerId" value="' + resourceId + '">'); |
|
16 |
- } |
|
17 |
- |
|
18 |
- form.ajaxForm({ success: fn }); |
|
19 |
- form.submit(); |
|
17 |
+ form.ajaxForm({ |
|
18 |
+ "success" : fn |
|
19 |
+ }); |
|
20 |
+ |
|
21 |
+ try { |
|
22 |
+ form.submit(); |
|
23 |
+ } finally { |
|
24 |
+ form = resourceType = resourceId = null; |
|
25 |
+ } |
|
20 | 26 |
}; |
21 | 27 |
|
22 |
-var fileUploader = function (target, textarea, action) { |
|
23 |
- var setProgressBar = function(value) { |
|
24 |
- progressbar.css("width", value + "%"); |
|
25 |
- progressbar.text(value + "%"); |
|
26 |
- }; |
|
28 |
+/** |
|
29 |
+ * fileUploader |
|
30 |
+ */ |
|
31 |
+var fileUploader = function(htOptions) { |
|
32 |
+ var target = htOptions.target; |
|
33 |
+ var textarea = htOptions.textarea; |
|
34 |
+ var action = htOptions.action; |
|
35 |
+ |
|
36 |
+ var setProgressBar = function(value) { |
|
37 |
+ progressbar.css("width", value + "%"); |
|
38 |
+ progressbar.text(value + "%"); |
|
39 |
+ }; |
|
27 | 40 |
|
28 |
- var createFileItem = function(file, link) { |
|
29 |
- var fileitem, filelink, filesize, insertButton, deleteButton; |
|
41 |
+ var createFileItem = function(file, link) { |
|
42 |
+ var fileitem, filelink, filesize, insertButton, deleteButton; |
|
30 | 43 |
|
31 |
- fileitem = $('<li>'); |
|
32 |
- fileitem.attr('tabindex', 0); |
|
44 |
+ fileitem = $('<li>'); |
|
45 |
+ fileitem.attr('tabindex', 0); |
|