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

Issue: edit label on issueView
@a3d11d5cb14c45bde9d3adf5182f4b5c79375b21
--- app/assets/stylesheets/less/_override.less
+++ app/assets/stylesheets/less/_override.less
... | ... | @@ -194,21 +194,43 @@ |
194 | 194 |
} |
195 | 195 |
|
196 | 196 |
.select2-container-multi { |
197 |
- |
|
198 | 197 |
.select2-choices { |
199 |
- border: 1px solid rgba(0,0,0,0.15); |
|
198 |
+ background: none; |
|
199 |
+ filter: none; |
|
200 |
+ border: none; |
|
200 | 201 |
|
201 | 202 |
.select2-search-choice { |
202 |
- border: 1px solid rgba(0,0,0,0.15); |
|
203 |
+ border: none; |
|
203 | 204 |
background-image:none; |
204 | 205 |
filter:none; |
206 |
+ } |
|
207 |
+ } |
|
208 |
+ |
|
209 |
+ &.issue-labels { |
|
210 |
+ width:auto !important; |
|
211 |
+ min-width:220px; |
|
212 |
+ |
|
213 |
+ .select2-choices { |
|
214 |
+ .select2-search-choice { |
|
215 |
+ background:transparent; |
|
216 |
+ filter:none; |
|
217 |
+ border:none; |
|
218 |
+ .box-shadow(none); |
|
219 |
+ } |
|
220 |
+ |
|
221 |
+ .select2-search-choice-close { |
|
222 |
+ top:6px; |
|
223 |
+ } |
|
205 | 224 |
} |
206 | 225 |
} |
207 | 226 |
|
208 | 227 |
&.select2-container-active { |
209 | 228 |
.select2-choices { |
210 | 229 |
border: 1px solid rgba(0,0,0,0.15); |
230 |
+ border-bottom:0; |
|
211 | 231 |
background-image:none; |
232 |
+ outline:none; |
|
233 |
+ .box-shadow(none); |
|
212 | 234 |
} |
213 | 235 |
} |
214 | 236 |
} |
--- app/assets/stylesheets/less/_yobiUI.less
+++ app/assets/stylesheets/less/_yobiUI.less
... | ... | @@ -325,6 +325,8 @@ |
325 | 325 |
&:hover { |
326 | 326 |
.opacity(70); |
327 | 327 |
} |
328 |
+ &.static.active { border-left:initial !important; } |
|
329 |
+ &.static:hover { .opacity(100); } |
|
328 | 330 |
} |
329 | 331 |
span.issue-label { |
330 | 332 |
display: inline-block; |
--- app/controllers/IssueApp.java
+++ app/controllers/IssueApp.java
... | ... | @@ -446,11 +446,15 @@ |
446 | 446 |
} |
447 | 447 |
|
448 | 448 |
if (issueMassUpdate.attachingLabel != null) { |
449 |
- issue.labels.add(issueMassUpdate.attachingLabel); |
|
449 |
+ for (IssueLabel label : issueMassUpdate.attachingLabel) { |
|
450 |
+ issue.labels.add(label); |
|
451 |
+ } |
|
450 | 452 |
} |
451 | 453 |
|
452 | 454 |
if (issueMassUpdate.detachingLabel != null) { |
453 |
- issue.labels.remove(issueMassUpdate.detachingLabel); |
|
455 |
+ for (IssueLabel label : issueMassUpdate.detachingLabel) { |
|
456 |
+ issue.labels.remove(label); |
|
457 |
+ } |
|
454 | 458 |
} |
455 | 459 |
|
456 | 460 |
issue.updatedDate = JodaDateUtil.now(); |
--- app/models/IssueMassUpdate.java
+++ app/models/IssueMassUpdate.java
... | ... | @@ -13,6 +13,6 @@ |
13 | 13 |
|
14 | 14 |
@Constraints.Required |
15 | 15 |
public List<Issue> issues; |
16 |
- public IssueLabel attachingLabel; |
|
17 |
- public IssueLabel detachingLabel; |
|
16 |
+ public List<IssueLabel> attachingLabel; |
|
17 |
+ public List<IssueLabel> detachingLabel; |
|
18 | 18 |
} |
--- app/views/issue/partial_massupdate.scala.html
+++ app/views/issue/partial_massupdate.scala.html
... | ... | @@ -79,7 +79,7 @@ |
79 | 79 |
</ul> |
80 | 80 |
</div> |
81 | 81 |
|
82 |
- <div id="attaching-label" class="btn-group" data-name="attachingLabel.id"> |
|
82 |
+ <div id="attaching-label" class="btn-group" data-name="attachingLabel[0].id"> |
|
83 | 83 |
<button class="btn dropdown-toggle medium" data-toggle="dropdown" disabled="disabled"> |
84 | 84 |
<span class="d-label">@Messages("issue.update.attachLabel")</span> |
85 | 85 |
<span class="d-caret"><span class="caret"></span></span> |
... | ... | @@ -89,7 +89,7 @@ |
89 | 89 |
</ul> |
90 | 90 |
</div> |
91 | 91 |
|
92 |
- <div id="detaching-label" class="btn-group" data-name="detachingLabel.id"> |
|
92 |
+ <div id="detaching-label" class="btn-group" data-name="detachingLabel[0].id"> |
|
93 | 93 |
<button class="btn dropdown-toggle medium" data-toggle="dropdown" disabled="disabled"> |
94 | 94 |
<span class="d-label">@Messages("issue.update.detachLabel")</span> |
95 | 95 |
<span class="d-caret"><span class="caret"></span></span> |
--- app/views/issue/view.scala.html
+++ app/views/issue/view.scala.html
... | ... | @@ -40,14 +40,35 @@ |
40 | 40 |
<!--board-body--> |
41 | 41 |
<div class="board-body row-fluid"> |
42 | 42 |
<div class="span9"> |
43 |
+ @if(IssueLabel.findByProject(project).size > 0){ |
|
43 | 44 |
<div class="pull-right" style="padding:20px;"> |
44 | 45 |
@**<!-- labels -->**@ |
45 | 46 |
<p class="labels-wrap"> |
46 |
- @for(label <- issue.labels.toList.sortBy(r => (r.category, r.name))) { |
|
47 |
- <a href='@routes.IssueApp.issues(project.owner, project.name, issue.state.state(), "html", 1)&labelIds=@label.id' class="label issue-label" data-labelId="@label.id" data-color="@label.color" style="background:@label.color">@label.name</a> |
|
47 |
+ @if(isAllowed(UserApp.currentUser(), issue.asResource(), Operation.UPDATE)) { |
|
48 |
+ <select id="issueLabels" multiple="multiple" data-placeholder="@Messages("label.select")" |
|
49 |
+ data-toggle="select2" data-format="issuelabel" data-containerCSSClass="issue-labels"> |
|
50 |
+ <option></option> |
|
51 |
+ @IssueLabel.findByProject(project).groupBy(_.category).map { |
|
52 |
+ case (category, labels) => { |
|
53 |
+ <optgroup label="@category"> |
|
54 |
+ @labels.map { label => |
|
55 |
+ <option value="@label.id" data-category="@category" @if(issue.labels.contains(label)){ selected }> |
|
56 |
+ @label.name |
|
57 |
+ </option> |
|
58 |
+ } |
|
59 |
+ </optgroup> |
|
60 |
+ } |
|
61 |
+ } |
|
62 |
+ </select> |
|
63 |
+ } else { |
|
64 |
+ @for(label <- issue.labels.toList.sortBy(r => (r.category, r.name))) { |
|
65 |
+ <a href='@routes.IssueApp.issues(project.owner, project.name, issue.state.state(), "html", 1)&labelIds=@label.id' class="label issue-label active static" data-labelId="@label.id" data-color="@label.color" style="background:@label.color">@label.name</a> |
|
66 |
+ } |
|
48 | 67 |
} |
49 | 68 |
</p> |
50 | 69 |
</div> |
70 |
+ } |
|
71 |
+ |
|
51 | 72 |
@if(StringUtils.isEmpty(issue.body)){ |
52 | 73 |
<div class="content empty-content"></div> |
53 | 74 |
} else { |
... | ... | @@ -63,7 +84,7 @@ |
63 | 84 |
|
64 | 85 |
@**<!-- author -->**@ |
65 | 86 |
<dl class="author"> |
66 |
- <dt>@Messages("igitssue.author")</dt> |
|
87 |
+ <dt>@Messages("issue.author")</dt> |
|
67 | 88 |
<dd style="padding:5px 10px;"> |
68 | 89 |
<a href="@routes.UserApp.userInfo(issue.authorLoginId)" class="usf-group"> |
69 | 90 |
<span class="avatar-wrap smaller"> |
--- conf/messages
+++ conf/messages
... | ... | @@ -237,6 +237,7 @@ |
237 | 237 |
issue.update.assignee = Update assignee |
238 | 238 |
issue.update.attachLabel = Attach label |
239 | 239 |
issue.update.detachLabel = Detach label |
240 |
+issue.update.label = Update label |
|
240 | 241 |
issue.update.milestone = Update milestone |
241 | 242 |
issue.update.state = Update state |
242 | 243 |
issue.vote = Sympathy |
--- conf/messages.ko
+++ conf/messages.ko
... | ... | @@ -237,6 +237,7 @@ |
237 | 237 |
issue.update.assignee = 담당자 변경 |
238 | 238 |
issue.update.attachLabel = 라벨 추가 |
239 | 239 |
issue.update.detachLabel = 라벨 제거 |
240 |
+issue.update.label = 라벨 변경 |
|
240 | 241 |
issue.update.milestone = 마일스톤 변경 |
241 | 242 |
issue.update.state = 상태 변경 |
242 | 243 |
issue.vote = 공감 |
--- public/javascripts/common/yobi.ui.Select2.js
+++ public/javascripts/common/yobi.ui.Select2.js
... | ... | @@ -33,7 +33,10 @@ |
33 | 33 |
var welSelect = $(elSelect); |
34 | 34 |
|
35 | 35 |
// Select2.js default options |
36 |
- var htOpt = $.extend({"width": "resolve"}, htOptions); |
|
36 |
+ var htOpt = $.extend({ |
|
37 |
+ "width": "resolve", |
|
38 |
+ "containerCssClass": welSelect.data("containercssclass") |
|
39 |
+ }, htOptions); |
|
37 | 40 |
|
38 | 41 |
// Customized formats |
39 | 42 |
var htFormat = { |
... | ... | @@ -79,6 +82,16 @@ |
79 | 82 |
}); |
80 | 83 |
|
81 | 84 |
return sText; |
85 |
+ }, |
|
86 |
+ "issuelabel": function(oItem){ |
|
87 |
+ var welItem = $(oItem.element); |
|
88 |
+ var sLabelId = welItem.val(); |
|
89 |
+ |
|
90 |
+ if(!sLabelId){ |
|
91 |
+ return '<span>' + oItem.text.trim() + '</span>'; |
|
92 |
+ } |
|
93 |
+ |
|
94 |
+ return '<a class="label issue-label active static" data-labelid="' + sLabelId + '">' + oItem.text.trim() + '</a>'; |
|
82 | 95 |
} |
83 | 96 |
}; |
84 | 97 |
|
--- public/javascripts/service/yobi.issue.View.js
+++ public/javascripts/service/yobi.issue.View.js
... | ... | @@ -41,11 +41,11 @@ |
41 | 41 |
|
42 | 42 |
htElement.welBtnWatch = $('#watch-button'); |
43 | 43 |
|
44 |
+ htElement.welIssueLabels = $("#issueLabels"); |
|
44 | 45 |
htElement.welAssignee = htOptions.welAssignee || $("#assignee"); |
45 | 46 |
htElement.welMilestone = htOptions.welMilestone || $("#milestone"); |
46 | 47 |
htElement.welIssueUpdateForm = htOptions.welIssueUpdateForm; |
47 | 48 |
|
48 |
- htElement.welChkIssueOpen = $("#issueOpen"); |
|
49 | 49 |
htElement.welTimelineWrap = $("#timeline"); |
50 | 50 |
htElement.welTimelineList = htElement.welTimelineWrap.find(".timeline-list"); |
51 | 51 |
} |
... | ... | @@ -75,6 +75,9 @@ |
75 | 75 |
htVar.sNextState = htOptions.sNextState; |
76 | 76 |
htVar.sNextStateUrl = htOptions.sNextStateUrl; |
77 | 77 |
htVar.sCommentWithStateUrl = htOptions.sCommentWithStateUrl; |
78 |
+ |
|
79 |
+ // for label update |
|
80 |
+ htVar.aLatestLabelIds = htElement.welIssueLabels.val(); |
|
78 | 81 |
} |
79 | 82 |
|
80 | 83 |
/** |
... | ... | @@ -85,9 +88,9 @@ |
85 | 88 |
htElement.welBtnWatch.click(_onClickBtnWatch); |
86 | 89 |
|
87 | 90 |
// 이슈 정보 업데이트 |
88 |
- htElement.welChkIssueOpen.change(_onChangeIssueOpen); |
|
89 | 91 |
htElement.welAssignee.on("change", _onChangeAssignee); |
90 | 92 |
htElement.welMilestone.on("change", _onChangeMilestone); |
93 |
+ htElement.welIssueLabels.on("change", _onChangeIssueLabels); |
|
91 | 94 |
|
92 | 95 |
// 타임라인 자동업데이트를 위한 정보 |
93 | 96 |
if(htElement.welTextarea.length > 0){ |
... | ... | @@ -134,27 +137,113 @@ |
134 | 137 |
} |
135 | 138 |
|
136 | 139 |
/** |
137 |
- * 이슈 해결/미해결 스위치 변경시 |
|
140 |
+ * 이슈 라벨 변경시 |
|
141 |
+ * change 이벤트 핸들러 |
|
142 |
+ * |
|
143 |
+ * @param weEvt |
|
144 |
+ * @private |
|
138 | 145 |
*/ |
139 |
- function _onChangeIssueOpen(){ |
|
140 |
- var welTarget = $(this); |
|
141 |
- var bChecked = welTarget.prop("checked"); |
|
142 |
- var sNextState = bChecked ? "OPEN" : "CLOSED"; |
|
146 |
+ function _onChangeIssueLabels(weEvt){ |
|
147 |
+ var htReqData = _getRequestDataForUpdateIssueLabel(weEvt); |
|
143 | 148 |
|
149 |
+ // 업데이트 요청 전송 |
|
144 | 150 |
_requestUpdateIssue({ |
145 |
- "htData" : {"state": sNextState}, |
|
146 |
- "fOnLoad": function(){ |
|
147 |
- welTarget.prop("checked", bChecked); |
|
148 |
- _updateTimeline(); |
|
149 |
- }, |
|
151 |
+ "htData" : htReqData, |
|
152 |
+ "fOnLoad" : function(){ |
|
153 |
+ $yobi.notify(Messages("issue.update.label"), 3000); |
|
154 |
+ }, |
|
150 | 155 |
"fOnError": function(oRes){ |
151 |
- welTarget.prop("checked", !bChecked); |
|
152 |
- _onErrorRequest(Messages("issue.update.state"), oRes); |
|
156 |
+ _onErrorRequest(Messages("issue.update.label"), oRes); |
|
153 | 157 |
} |
154 | 158 |
}); |
155 | 159 |
} |
156 | 160 |
|
157 | 161 |
/** |
162 |
+ * 이슈 라벨 변경 요청 데이터를 반환 |
|
163 |
+ * |
|
164 |
+ * @param weEvt |
|
165 |
+ * @returns {Hash Table} |
|
166 |
+ * @private |
|
167 |
+ */ |
|
168 |
+ function _getRequestDataForUpdateIssueLabel(weEvt){ |
|
169 |
+ var htReqData = {}; |
|
170 |
+ |
|
171 |
+ // 삭제할 라벨 |
|
172 |
+ htReqData["detachingLabel[0].id"] = _getIdPropFromObject(weEvt.removed); |
|
173 |
+ |
|
174 |
+ // 추가할 라벨 |
|
175 |
+ htReqData["attachingLabel[0].id"] = _getIdPropFromObject(weEvt.added); |
|
176 |
+ |
|
177 |
+ // 추가하는 라벨이 있는 경우 |
|
178 |
+ // 해당 라벨을 추가함으로 인해 삭제해야 하는 라벨을 찾아 htReqData 에 넣는다 |
|
179 |
+ if(htReqData["attachingLabel[0].id"]){ |
|
180 |
+ var htRemove = _getLabelsToRemovedByAdding(weEvt.added); |
|
181 |
+ htReqData = $.extend(htReqData, htRemove); |
|
182 |
+ } |
|
183 |
+ |
|
184 |
+ return htReqData; |
|
185 |
+ } |
|
186 |
+ |
|
187 |
+ /** |
|
188 |
+ * {@code htItem}의 id 속성을 반환한다 |
|
189 |
+ * |
|
190 |
+ * @param htItem |
|
191 |
+ * @returns {*} |
|
192 |
+ * @private |
|
193 |
+ */ |
|
194 |
+ function _getIdPropFromObject(htItem){ |
|
195 |
+ return (htItem && htItem.id) ? htItem.id : undefined; |
|
196 |
+ } |
|
197 |
+ |
|
198 |
+ /** |
|
199 |
+ * {@code htLabel} 을 추가함으로 인해 삭제해야 하는 라벨을 찾아 그 정보를 반환한다 |
|
200 |
+ * |
|
201 |
+ * @param htLabel |
|
202 |
+ * @private |
|
203 |
+ * @return {Hash Table} |
|
204 |
+ */ |
|
205 |
+ function _getLabelsToRemovedByAdding(htLabel){ |
|
206 |
+ var htRemove = {}; |
|
207 |
+ var oIssueLabels = htElement.welIssueLabels.data("select2"); |
|
208 |
+ var aIssueLabelValues = oIssueLabels.val(); |
|
209 |
+ var aRemoveLabelIds = _getLabelInSameCategoryWith(oIssueLabels.data(), htLabel); |
|
210 |
+ |
|
211 |
+ // 삭제할 항목으로 추가하고 |
|
212 |
+ aRemoveLabelIds.forEach(function(nValue, nIndex){ |
|
213 |
+ htRemove["detachingLabel[" + (nIndex + 1) + "].id"] = nValue; |
|
214 |
+ aIssueLabelValues.splice(aIssueLabelValues.indexOf(nValue), 1); |
|
215 |
+ }); |
|
216 |
+ |
|
217 |
+ // 해당 항목이 제거된 상태로 Select2 값 설정 |
|
218 |
+ oIssueLabels.val(aIssueLabelValues); |
|
219 |
+ |
|
220 |
+ return htRemove; |
|
221 |
+ } |
|
222 |
+ |
|
223 |
+ /** |
|
224 |
+ * {@code aData} 를 기준으로 {@code htAddedLabel}과 같은 카테고리의 항목을 반환한다 |
|
225 |
+ * |
|
226 |
+ * @param aData |
|
227 |
+ * @param htAddedLabel |
|
228 |
+ * @private |
|
229 |
+ * @returns {Array} |
|
230 |
+ */ |
|
231 |
+ function _getLabelInSameCategoryWith(aData, htAddedLabel){ |
|
232 |
+ var aLabelIds = []; |
|
233 |
+ var sAddedCategory = $(htAddedLabel.element).data("category"); |
|
234 |
+ |
|
235 |
+ aData.forEach(function(htData){ |
|
236 |
+ var sCategory = $(htData.element).data("category"); |
|
237 |
+ |
|
238 |
+ if(htData.id !== htAddedLabel.id && sCategory === sAddedCategory){ |
|
239 |
+ aLabelIds.push(htData.id); |
|
240 |
+ } |
|
241 |
+ }); |
|
242 |
+ |
|
243 |
+ return aLabelIds; |
|
244 |
+ } |
|
245 |
+ |
|
246 |
+ /** |
|
158 | 247 |
* 담당자 변경시 |
159 | 248 |
* |
160 | 249 |
* @param {Wrapped Event} weEvt "change" 이벤트 |
Add a comment
Delete comment
Once you delete this comment, you won't be able to recover it. Are you sure you want to delete this comment?