THIS IS A TEST INSTANCE ONLY! REPOSITORIES CAN BE DELETED AT ANY TIME!

test
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

587 lines
30KB

  1. @**
  2. * Yona, 21st Century Project Hosting SW
  3. *
  4. * Copyright Yona & Yobi Authors & NAVER Corp. & NAVER LABS Corp.
  5. * https://yona.io
  6. **@
  7. @(title:String, issue:Issue, issueForm: play.data.Form[Issue], commentForm: play.data.Form[Comment],project:Project)
  8. @import scala.collection.mutable.ArrayBuffer
  9. @import org.apache.commons.lang.StringUtils
  10. @import models.enumeration.ResourceType
  11. @import models.enumeration.Operation
  12. @import models.Milestone
  13. @import utils.JodaDateUtil
  14. @import utils.TemplateHelper._
  15. @import utils.AccessControl._
  16. @import play.libs.Json.toJson
  17. @import utils.Markdown
  18. @import models.enumeration.State
  19. @import controllers.api.IssueApi
  20. @getTitle(issue:Issue) = @{ issue.title }
  21. @isFirstState(state:State) = {@if(issue.state.state == Issue.availableStates.get(0).state()){dirty}}
  22. @urlToIssues = @{
  23. requestHeader.headers.get("Referer") match {
  24. case Some(u) => urlToList(u, routes.IssueApp.issues(project.owner, project.name, issue.state.state()).toString())
  25. case _ => routes.IssueApp.issues(project.owner, project.name, "open").toString()
  26. }
  27. }
  28. @isVotedByCurrentUser = @{
  29. issue.isVotedBy(UserApp.currentUser)
  30. }
  31. @urlToVote = @{
  32. if(isVotedByCurrentUser){
  33. routes.VoteApp.unvote(project.owner, project.name, issue.getNumber).toString
  34. } else {
  35. routes.VoteApp.vote(project.owner, project.name, issue.getNumber).toString
  36. }
  37. }
  38. @getVoteButtonTitle = {
  39. @if(isVotedByCurrentUser){
  40. @Messages("issue.unvote.description")
  41. } else {
  42. @Messages("issue.vote.description")
  43. }
  44. }
  45. @titleForOGTag = @{getTitle(issue) + " |:| " + issue.body.substring(0, Math.min(issue.body.length, 200))}
  46. @parentIssueId = @{
  47. if(issue.parent != null){
  48. issue.parent.id
  49. } else {
  50. issue.id
  51. }
  52. }
  53. @hasAssignee = @{
  54. issue.assigneeName != null
  55. }
  56. @hasSharer = @{
  57. issue.sharers.size > 0
  58. }
  59. @sharers = @{
  60. var sharerIds = ArrayBuffer[String]()
  61. for( sharedUser <- issue.sharers ) {
  62. sharerIds += sharedUser.loginId
  63. }
  64. sharerIds.mkString(",")
  65. }
  66. @VOTER_AVATAR_SHOW_LIMIT = @{ 5 }
  67. @conatinsCurrentUserInWatchers = @{Watch.isWatching(UserApp.currentUser(), issue.asResource())}
  68. @isThisParentIssue() = @{
  69. issue.parent == null
  70. }
  71. @amIShared() = @{
  72. var found = false
  73. for(sharer <- issue.sharers) {
  74. if(sharer.user.id.equals(UserApp.currentUser().id)) {
  75. found = true
  76. }
  77. }
  78. found
  79. }
  80. @isFavoriteIssue = @{
  81. FavoriteIssue.findByIssueId(UserApp.currentUser().id, issue.id) != null
  82. }
  83. @isAllowedUpdate = @{
  84. isAllowed(UserApp.currentUser(), issue.asResource(), Operation.UPDATE)
  85. }
  86. @projectLayout(titleForOGTag, project, utils.MenuType.ISSUE){
  87. @projectMenu(project, utils.MenuType.ISSUE, "main-menu-only")
  88. <div class="page-wrap-outer">
  89. <div class="project-page-wrap board-view">
  90. @** Post Info **@
  91. <div class="board-header issue">
  92. <div class="pull-right mr10 mt10 hide-in-mobile">
  93. <div class="date" title="@JodaDateUtil.getDateString(issue.createdDate)">
  94. @agoOrDateString(issue.createdDate)
  95. </div>
  96. <span class="badge badge-issue-@issue.state.state.toLowerCase">@Messages("issue.state." + issue.state.state)</span>
  97. </div>
  98. <div class="title">
  99. @if(issue.parent != null) {
  100. <span class="subtask-mark">subtask</span>
  101. }
  102. <strong class="board-id">#@issue.getNumber</strong> @issue.title
  103. <span class="favorite-issue" data-issue-id="@issue.id">
  104. <i class="@if(isFavoriteIssue){starred} star material-icons va-text-top">star</i>
  105. </span>
  106. <div class="pull-right hide show-in-mobile" style="font-size: 0.7em">
  107. <span class="date" title="@JodaDateUtil.getDateString(issue.createdDate)">
  108. @agoOrDateString(issue.createdDate)
  109. </span>
  110. <span class="badge badge-small badge-issue-@issue.state.state.toLowerCase">@Messages("issue.state." + issue.state.state)</span>
  111. </div>
  112. </div>
  113. </div>
  114. @** Content body **@
  115. <!--board-body-->
  116. <div class="board-body row-fluid">
  117. <div class="span9 span-left-pane">
  118. <div class="author-info">
  119. <a href="@userInfo(issue.authorLoginId)" class="usf-group">
  120. <span class="avatar-wrap smaller">
  121. <img src="@User.findByLoginId(issue.authorLoginId).avatarUrl(32)" width="20" height="20">
  122. </span>
  123. @if(issue.authorLoginId != null){
  124. <strong class="name">@issue.getAuthor.getDisplayName</strong>
  125. <span class="loginid"> <strong>@{"@"}</strong>@issue.authorLoginId</span>
  126. } else {
  127. <strong class="name">@Messages("issue.noAuthor")</strong>
  128. }
  129. </a>
  130. @if(StringUtils.isNotEmpty(issue.history)){
  131. @if(UserApp.currentUser().isAnonymous){
  132. <div class="posting-history">
  133. <a href="@routes.UserApp.loginForm()?redirectUrl=@routes.IssueApp.issue(project.owner, project.name, issue.getNumber)" data-toggle="modal">@Messages("change.history")</a>
  134. </div>
  135. } else {
  136. <div class="posting-history">
  137. <a href="#-yona-posting-history" data-toggle="modal">@Messages("change.history")</a>
  138. @common.partial_history(issue)
  139. </div>
  140. }
  141. }
  142. </div>
  143. @if(StringUtils.isEmpty(issue.body)){
  144. <div class="content empty-content"></div>
  145. } else {
  146. <div id="issue-@issue.getNumber" class="hide">
  147. <form action="@api.routes.IssueApi.updateIssueContent(project.owner, project.name, issue.getNumber)">
  148. <textarea>@issue.body</textarea>
  149. </form>
  150. </div>
  151. <div id="issue-body-@issue.getNumber">
  152. @common.tasklistBar()
  153. <div class="content markdown-wrap" data-allowed-update="@isAllowedUpdate">@Html(Markdown.render(issue.body, issue.project))</div>
  154. </div>
  155. }
  156. <div class="attachments" id="attachments" data-attachments="@toJson(AttachmentApp.getFileList(ResourceType.ISSUE_POST.toString(), issue.id.toString()))"></div>
  157. <div class="board-actrow right-txt">
  158. <div class="pull-left">
  159. <div>
  160. @if(isAllowed(UserApp.currentUser(), issue.asResource(), Operation.WATCH)) {
  161. <button id="watch-button" type="button" class="ybtn @if(conatinsCurrentUserInWatchers) {ybtn-watching}"
  162. data-toggle="tooltip" data-placement="top" title="@Messages("issue.watch.description")" data-watching="@conatinsCurrentUserInWatchers">
  163. @if(conatinsCurrentUserInWatchers) {
  164. @Messages("issue.unwatch")
  165. } else {
  166. @Messages("issue.watch")
  167. }
  168. </button>
  169. }
  170. @if(isAllowedUpdate && !hasSharer) {
  171. <button id="issue-share-button" type="button" class="ybtn" data-toggle="popover" data-trigger="hover" data-placement="top" data-content="@Messages("issue.sharer.description")">@Messages("button.share.issue")</button>
  172. }
  173. </div>
  174. </div>
  175. <div id="vote" class="vote-wrap @if(issue.voters.size > 0){voter-exists}">
  176. @if(isResourceCreatable(UserApp.currentUser, issue.asResource(), ResourceType.ISSUE_COMMENT)) {
  177. <a href="@urlToVote" class="@if(issue.isVotedBy(UserApp.currentUser)) {ybtn-watching}" title="@getVoteButtonTitle"
  178. data-request-method="post" data-toggle="tooltip">
  179. <span class="heart"><i class="yobicon-hearts"></i></span>
  180. </a>
  181. } else {
  182. <span class="ybtn-disabled" style="color: #777;" data-toggle="tooltip" title="@Messages("user.login.alert")" data-login="required">
  183. <span class="heart"><i class="yobicon-hearts"></i></span>
  184. </span>
  185. }
  186. @if(issue.voters.size > 0) {
  187. @partial_voters(issue, 3)
  188. }
  189. </div>
  190. @if(issue.voters.size > 0) {
  191. @partial_voter_list("voters", issue.voters)
  192. }
  193. @if(StringUtils.isNotBlank(IssueApi.TRANSLATION_API)){
  194. <button type="button" id="translate" class="icon btn-transparent-with-fontsize-lineheight ml10" data-toggle="tooltip" title="@Messages("button.translation")"><i class="yobicon-lang"></i></button>
  195. }
  196. <span class="act-row">
  197. @if(isAllowedUpdate) {
  198. <button type="button" class="icon btn-transparent-with-fontsize-lineheight ml10 pt5px" data-toggle="tooltip" title="@Messages("button.edit")" onclick="window.location='@routes.IssueApp.editIssueForm(project.owner, project.name, issue.getNumber)'"><i class="yobicon-edit-2"></i></button>
  199. } else {
  200. <a href="@routes.IssueApp.editIssueForm(project.owner, project.name, issue.getNumber)"><button type="button" class="icon btn-transparent-with-fontsize-lineheight ml10 pt5px" data-toggle="tooltip" title="@Messages("button.show.original")"><i class="yobicon-edit-2"></i></button></a>
  201. }
  202. @if(issue.canBeDeleted) {
  203. @if(isAllowed(UserApp.currentUser(), issue.asResource(), Operation.DELETE)) {
  204. <a href="#deleteConfirm" data-toggle="modal">
  205. <button type="button" class="icon btn-transparent-with-fontsize-lineheight ml6" data-toggle='tooltip' title="@Messages("button.delete")"><i class="yobicon-trash"></i></button></a>
  206. }
  207. } else {
  208. <button type="button" class="icon disabled btn-transparent-with-fontsize-lineheight ml6" data-toggle='popover' data-trigger="hover" data-placement="top" data-content="@Messages("issue.can.not.be.deleted")"><i class="yobicon-trash"></i></button>
  209. }
  210. </span>
  211. </div>
  212. <dl class="sharer-list @if(!hasSharer){hideFromDisplayOnly}">
  213. <dt class="issue-share-title mb10">
  214. @Messages("issue.sharer") <span class="num issue-sharer-count">@if(issue.sharers.size > 0) { @issue.sharers.size }</span>
  215. </dt>
  216. <dd id="sharer-list" class="@if(!hasSharer){hideFromDisplayOnly}">
  217. @if(isAllowedUpdate) {
  218. <input type="hidden" class="bigdrop width100p" id="issueSharer" name="issueSharer" placeholder="@Messages("issue.sharer.select")" value="@sharers" title="">
  219. } else {
  220. @for(sharer <- issue.getSortedSharer){
  221. <div class="text-ellipsis sharer-item">
  222. <a href="@userInfo(sharer.loginId)" class="usf-group">
  223. <strong class="name">@sharer.user.getDisplayName</strong>
  224. </a>
  225. </div>
  226. }
  227. }
  228. </dd>
  229. </dl>
  230. <div class="watcher-list"></div>
  231. <div class="subtasks">
  232. @if(amIShared) {
  233. @if(isThisParentIssue) {
  234. @partial_view_childIssueList(issue, project)
  235. }
  236. } else {
  237. @partial_view_childIssueList(issue, project)
  238. }
  239. </div>
  240. @** Comment **@
  241. <div id="comments" class="board-comment-wrap">
  242. <div id="timeline">
  243. <div class="timeline-list">
  244. @partial_comments(project, issue)
  245. </div>
  246. </div>
  247. @common.commentForm(issue.asResource(), ResourceType.ISSUE_COMMENT, routes.IssueApp.newComment(project.owner, project.name, issue.getNumber).toString())
  248. </div>
  249. @** // Comment **@
  250. </div>
  251. <div class="span3 span-right-pane mb20">
  252. <div class="issue-info">
  253. <form id="issueUpdateForm" action="@routes.IssueApp.massUpdate(project.owner, project.name)" method="post">
  254. <input type="hidden" name="issues[0].id" value="@issue.id" />
  255. @**<!-- assignee -->**@
  256. <dl>
  257. @if(project.menuSetting.issue) {
  258. <dd class="project-btn-item">
  259. <a href="@routes.IssueApp.newIssueForm(project.owner, project.name)?parentIssueId=@parentIssueId" class="ybtn ybtn-success">@Messages("button.newSubtask")</a>
  260. </dd>
  261. }
  262. <dt>@Messages("issue.assignee")</dt>
  263. <dd>
  264. @if(isAllowedUpdate) {
  265. @partial_assignee(project, issue)
  266. } else {
  267. @if(hasAssignee){
  268. <a href="@userInfo(issue.assignee.user.loginId)" class="usf-group">
  269. <span class="avatar-wrap smaller">
  270. <img src="@User.findByLoginId(issue.assignee.user.loginId).avatarUrl" width="20" height="20">
  271. </span>
  272. <strong class="name">@issue.assignee.user.getDisplayName</strong>
  273. <span class="loginid"> <strong>@{"@"}</strong>@issue.assignee.user.loginId</span>
  274. </a>
  275. } else {
  276. <div>
  277. @Messages("issue.noAssignee")
  278. </div>
  279. }
  280. }
  281. </dd>
  282. </dl>
  283. @**<!-- // -->**@
  284. @**<!-- milestones -->**@
  285. @if(project.menuSetting.milestone) {
  286. <dl>
  287. <dt>@Messages("milestone")</dt>
  288. <dd>
  289. @if(Milestone.findByProjectId(project.id).isEmpty()){
  290. <a href="@routes.MilestoneApp.newMilestoneForm(project.owner, project.name)"
  291. class="ybtn ybtn-small ybtn-fullsize" target="_blank">
  292. @Messages("milestone.menu.new")
  293. </a>
  294. } else {
  295. @defining(issue.milestone != null) { hasMilestone =>
  296. @if(isAllowedUpdate) {
  297. <select id="milestone" name="milestone.id"
  298. data-toggle="select2" data-format="milestone" data-container-css-class="fullsize">
  299. <option value="@Milestone.NULL_MILESTONE_ID" @if(!hasMilestone){ selected }>
  300. @Messages("issue.noMilestone")
  301. </option>
  302. <optgroup label="@Messages("milestone.state.open")">
  303. @for(milestone <- Milestone.findOpenMilestones(project.id)){
  304. <option value="@milestone.id" data-state="@milestone.state"
  305. @if(hasMilestone && issue.milestone.id == milestone.id){
  306. selected
  307. }>
  308. @milestone.title
  309. </option>
  310. }
  311. </optgroup>
  312. <optgroup label="@Messages("milestone.state.closed")">
  313. @for(milestone <- Milestone.findClosedMilestones(project.id)){
  314. <option value="@milestone.id" data-state="@milestone.state"
  315. @if(hasMilestone && issue.milestone.id == milestone.id){
  316. selected
  317. }>
  318. @milestone.title
  319. </option>
  320. }
  321. </optgroup>
  322. </select>
  323. } else {
  324. @if(hasMilestone){
  325. <a href="@routes.MilestoneApp.milestone(project.owner, project.name, issue.milestone.id)">
  326. @issue.milestone.title
  327. </a>
  328. } else {
  329. @Messages("issue.noMilestone")
  330. }
  331. }
  332. }
  333. }
  334. </dd>
  335. </dl>
  336. }
  337. @**<!-- // -->**@
  338. <dl>
  339. <dt>
  340. @Messages("issue.dueDate")
  341. <span class="duedate-status @if(issue.isOverDueDate) {overdue}">
  342. @if(issue.dueDate != null && issue.isOpen) {
  343. @if(issue.isOverDueDate) {
  344. (@Messages("issue.dueDate.overdue"))
  345. } else {
  346. (@issue.until)
  347. }
  348. }
  349. </span>
  350. </dt>
  351. <dd>
  352. @if(isAllowedUpdate) {
  353. <div class="search search-bar">
  354. <input type="text" name="dueDate" value="@issue.getDueDateString" class="textbox full" autocomplete="off" data-toggle="calendar">
  355. <button type="button" class="search-btn btn-calendar"><i class="yobicon-calendar2"></i></button>
  356. </div>
  357. } else {
  358. @if(issue.dueDate != null) {
  359. @issue.getDueDateString
  360. } else {
  361. @Messages("issue.noDuedate")
  362. }
  363. }
  364. </dd>
  365. </dl>
  366. @**<!-- labels -->**@
  367. @if(!IssueLabel.findByProject(project).isEmpty){
  368. @if(isAllowedUpdate){
  369. @partial_select_label(IssueLabel.findByProject(project), issue.getLabelIds, "", "", project)
  370. } else {
  371. @partial_show_selected_label(issue.labels.toList, routes.IssueApp.issues(project.owner, project.name, issue.state.state(), "html", 1).toString)
  372. }
  373. }
  374. <div class="act-row right-menu-icons">
  375. @if(isAllowedUpdate) {
  376. <button type="button" class="icon btn-transparent-with-fontsize-lineheight ml10 pt5px" data-toggle="tooltip" title="@Messages("button.edit")" onclick="window.location='@routes.IssueApp.editIssueForm(project.owner, project.name, issue.getNumber)'"><i class="yobicon-edit-2"></i></button>
  377. } else {
  378. <a href="@routes.IssueApp.editIssueForm(project.owner, project.name, issue.getNumber)"><button type="button" class="icon btn-transparent-with-fontsize-lineheight ml10 pt5px" data-toggle="tooltip" title="@Messages("button.show.original")"><i class="yobicon-edit-2"></i></button></a>
  379. }
  380. @if(issue.canBeDeleted) {
  381. @if(isAllowed(UserApp.currentUser(), issue.asResource(), Operation.DELETE)) {
  382. <a href="#deleteConfirm" data-toggle="modal">
  383. <button type="button" class="icon btn-transparent-with-fontsize-lineheight ml6" data-toggle='tooltip' title="@Messages("button.delete")"><i class="yobicon-trash"></i></button></a>
  384. }
  385. } else {
  386. <button type="button" class="icon disabled btn-transparent-with-fontsize-lineheight ml6" data-toggle='popover' data-trigger="hover" data-placement="top" data-content="@Messages("issue.can.not.be.deleted")"><i class="yobicon-trash"></i></button>
  387. }
  388. </div>
  389. @**<!-- // -->**@
  390. </form>
  391. </div>
  392. </div>
  393. </div>
  394. <div class="board-footer">
  395. @help.keymap("issueDetail", project)
  396. </div>
  397. </div>
  398. <script type="text/x-jquery-tmpl" id="tplAttachedFile"><!--
  399. --><li class="attached-file" data-name="${fileName}" data-href="${fileHref}" data-mime="${mimeType}" data-size="${fileSize}">
  400. <strong>${fileName}(${fileSizeReadable})${notice}</strong><!--
  401. --><a class="attached-delete"><i class="ico btn-delete"></i></a></li>
  402. </script>
  403. @** Confirm to delete post **@
  404. <div id="deleteConfirm" class="modal hide fade">
  405. <div class="modal-header">
  406. <button type="button" class="close" data-dismiss="modal">×</button>
  407. <h3>@Messages("issue.delete")</h3>
  408. </div>
  409. <div class="modal-body">
  410. <p>@Messages("post.delete.confirm")</p>
  411. </div>
  412. <div class="modal-footer">
  413. <button type="button" class="ybtn ybtn-danger" data-request-method="delete" data-request-uri="@routes.IssueApp.deleteIssue(project.owner, project.name, issue.getNumber)">@Messages("button.yes")</button>
  414. <button type="button" class="ybtn" data-dismiss="modal">@Messages("button.no")</button>
  415. </div>
  416. </div>
  417. </div>
  418. @common.markdown(project)
  419. @common.commentDeleteModal()
  420. @common.select2()
  421. @common.calendar()
  422. <link rel="stylesheet" type="text/css" media="screen" href="@routes.IssueLabelApp.labelStyles(project.owner, project.name)">
  423. <link rel="stylesheet" type="text/css" media="screen" href="@routes.Assets.at("javascripts/lib/atjs/jquery.atwho.css")">
  424. <link rel="stylesheet" type="text/css" media="screen" href="@routes.Assets.at("javascripts/lib/elevator/jquery.elevator.css")">
  425. <script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.caret.min.js")"></script>
  426. <script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.atwho.js")"></script>
  427. <script type="text/javascript" src="@routes.Assets.at("javascripts/lib/elevator/jquery.elevator.js")"></script>
  428. <script type="text/javascript" src="@routes.Assets.at("javascripts/service/yona.issue.Assginee.js")"></script>
  429. <script type="text/javascript" src="@routes.Assets.at("javascripts/service/yona.issue.Sharer.js")"></script>
  430. <script type="text/javascript" src="@routes.Assets.at("javascripts/common/yona.Sha1.js")"></script>
  431. <script type="text/javascript" src="@routes.Assets.at("javascripts/common/yona.Tasklist.js")"></script>
  432. <script type="text/javascript" src="@routes.Assets.at("javascripts/common/yona.SubComment.js")"></script>
  433. <script type="text/javascript">
  434. $(function(){
  435. // yobi.issue.View
  436. $yobi.loadModule("issue.View", {
  437. "issueId" : "@issue.id",
  438. "nextState": "@issue.nextState().toString.toLowerCase",
  439. "urls" : {
  440. "watch" : "@routes.WatchApp.watch(issue.asResource.asParameter)",
  441. "unwatch" : "@routes.WatchApp.unwatch(issue.asResource.asParameter)",
  442. "timeline" : "@routes.IssueApp.timeline(project.owner, project.name, issue.getNumber)",
  443. "nextState" : "@routes.IssueApp.nextState(project.owner, project.name, issue.getNumber)",
  444. "massUpdate": "@routes.IssueApp.massUpdate(project.owner, project.name)"
  445. }
  446. });
  447. // yobi.ShortcutKey
  448. yobi.ShortcutKey.setKeymapLink({
  449. "L": "@urlToIssues"
  450. @if(project.menuSetting.issue) {
  451. ,"N": "@routes.IssueApp.newIssueForm(project.owner, project.name)"
  452. }
  453. @if(isAllowedUpdate) {
  454. ,"E": "@routes.IssueApp.editIssueForm(project.owner, project.name, issue.getNumber)"
  455. }
  456. });
  457. // yobi.Mention
  458. yobi.Mention({
  459. "target": 'textarea[id^=editor-]',
  460. "url" : "@Html(routes.ProjectApp.mentionList(project.owner, project.name, issue.getNumber, issue.asResource().getType.resource()).toString())"
  461. });
  462. // detect comment which contains mention at me
  463. $(".comment-body:contains('@UserApp.currentUser().getPureNameOnly')").closest(".comment").addClass("mentioned");
  464. });
  465. </script>
  466. <script>
  467. $(function () {
  468. yonaAssgineeModule(
  469. "@api.routes.IssueApi.findAssignableUsers(project.owner, project.name, issue.getNumber)",
  470. "@api.routes.IssueApi.updateAssginees(project.owner, project.name, issue.getNumber)",
  471. "@Messages("issue.assignee")"
  472. );
  473. yonaIssueSharerModule(
  474. "@api.routes.IssueApi.findSharerByloginIds(project.owner, project.name, issue.getNumber)",
  475. "@api.routes.IssueApi.findSharableUsers(project.owner, project.name, issue.getNumber)",
  476. "@api.routes.IssueApi.updateSharer(project.owner, project.name, issue.getNumber)",
  477. "@Messages("issue.sharer")"
  478. );
  479. $('#issue-share-button').on('click', function () {
  480. $('#sharer-list').show();
  481. $('.sharer-list').show().addClass("sharer-list-border");
  482. });
  483. $('#translate').one('click', function (e) {
  484. var data = {
  485. owner: "@project.owner",
  486. projectName: "@project.name",
  487. type: "issue",
  488. number: "@issue.getNumber"
  489. };
  490. $.ajax({
  491. url: "/-_-api/v1/translation",
  492. data: JSON.stringify(data),
  493. type: "POST",
  494. dataType: "json",
  495. contentType: "application/json"
  496. }).done(function (data) {
  497. $(".markdown-wrap").first().html(data.translated);
  498. $(this).attr("disabled", true);
  499. });
  500. });
  501. $('.comment-translate').one('click', function (e) {
  502. var payload = {
  503. owner: "@project.owner",
  504. projectName: "@project.name",
  505. type: "issue-comment",
  506. number: $(this).data("commentId")
  507. };
  508. $.ajax({
  509. url: "/-_-api/v1/translation",
  510. data: JSON.stringify(payload),
  511. type: "POST",
  512. dataType: "json",
  513. contentType: "application/json"
  514. }).done(function (data) {
  515. $("#comment-body-" + payload.number).find(".markdown-wrap").html(data.translated)
  516. $(this).attr("disabled", true);
  517. });
  518. });
  519. if (ClipboardJS.isSupported()) {
  520. var clipboard = new ClipboardJS('#copyEmailBtn');
  521. clipboard.on('success', function(e) {
  522. $yobi.alert('@Messages("button.copy.email.success.message")');
  523. });
  524. } else {
  525. $yobi.notify('@Messages("site.features.error.clipboard")', 1500);
  526. }
  527. // timeline label text color adjusting
  528. $(".event > .label").each(function() {
  529. var $this = $(this);
  530. $this.removeClass("dimgray white")
  531. .addClass($yobi.getContrastColor($this.css('background-color')))
  532. });
  533. $.elevator({
  534. shape: 'rounded',
  535. tooltips: true
  536. });
  537. });
  538. </script>
  539. }