
Fix server-side diff rendering.
* Remove unnecssary codes. * Fix some bugs.
@b675b611139e01719941f1d03c140a5392bd3f72
--- app/controllers/CodeHistoryApp.java
+++ app/controllers/CodeHistoryApp.java
... | ... | @@ -109,7 +109,7 @@ |
109 | 109 |
List<Commit> commits = repository.getHistory(page, HISTORY_ITEM_LIMIT, branch, path); |
110 | 110 |
|
111 | 111 |
if (commits == null) { |
112 |
- return notFound(ErrorViews.NotFound.render("error.notfound", project, null)); |
|
112 |
+ return notFound(ErrorViews.NotFound.render("error.notfound", project)); |
|
113 | 113 |
} |
114 | 114 |
|
115 | 115 |
return ok(history.render(project, commits, page, branch, path)); |
... | ... | @@ -156,11 +156,11 @@ |
156 | 156 |
Commit parentCommit = repository.getParentCommitOf(commitId); |
157 | 157 |
|
158 | 158 |
if (fileDiffs == null) { |
159 |
- return notFound(ErrorViews.NotFound.render("error.notfound", project, null)); |
|
159 |
+ return notFound(ErrorViews.NotFound.render("error.notfound", project)); |
|
160 | 160 |
} |
161 | 161 |
|
162 | 162 |
if (patch == null) { |
163 |
- return notFound(ErrorViews.NotFound.render("error.notfound", project, null)); |
|
163 |
+ return notFound(ErrorViews.NotFound.render("error.notfound", project)); |
|
164 | 164 |
} |
165 | 165 |
|
166 | 166 |
List<CommitComment> comments = CommitComment.find.where().eq("commitId", |
--- app/controllers/IssueApp.java
+++ app/controllers/IssueApp.java
... | ... | @@ -205,7 +205,7 @@ |
205 | 205 |
|
206 | 206 |
Project project = Project.findByOwnerAndProjectName(ownerName, projectName); |
207 | 207 |
if (project == null) { |
208 |
- return notFound(ErrorViews.NotFound.render("error.notfound", project, null)); |
|
208 |
+ return notFound(ErrorViews.NotFound.render("error.notfound", project)); |
|
209 | 209 |
} |
210 | 210 |
|
211 | 211 |
int updatedItems = 0; |
--- app/controllers/PullRequestApp.java
+++ app/controllers/PullRequestApp.java
... | ... | @@ -730,18 +730,18 @@ |
730 | 730 |
return forbidden(ErrorViews.Forbidden.render("error.forbidden", project)); |
731 | 731 |
} |
732 | 732 |
|
733 |
- String patch = RepositoryService.getRepository(project).getPatch(commitId); |
|
733 |
+ List<FileDiff> fileDiffs = RepositoryService.getRepository(project).getDiff(commitId); |
|
734 | 734 |
Commit commit = RepositoryService.getRepository(project).getCommit(commitId); |
735 | 735 |
Commit parentCommit = RepositoryService.getRepository(project).getParentCommitOf(commitId); |
736 | 736 |
|
737 |
- if (patch == null) { |
|
738 |
- return notFound(ErrorViews.NotFound.render("error.notfound", project, null)); |
|
737 |
+ if (fileDiffs == null) { |
|
738 |
+ return notFound(ErrorViews.NotFound.render("error.notfound", project)); |
|
739 | 739 |
} |
740 | 740 |
|
741 | 741 |
List<CommitComment> comments = CommitComment.find.where().eq("commitId", |
742 | 742 |
commitId).eq("project.id", project.id).findList(); |
743 | 743 |
|
744 |
- return ok(diff.render(pullRequest, commit, parentCommit, patch, comments)); |
|
744 |
+ return ok(diff.render(pullRequest, commit, parentCommit, fileDiffs, comments)); |
|
745 | 745 |
} |
746 | 746 |
|
747 | 747 |
|
--- app/playRepository/DiffLine.java
+++ app/playRepository/DiffLine.java
... | ... | @@ -5,11 +5,14 @@ |
5 | 5 |
public final Integer numA; |
6 | 6 |
public final Integer numB; |
7 | 7 |
public final String content; |
8 |
+ public FileDiff file; |
|
8 | 9 |
|
9 |
- public DiffLine(DiffLineType type, Integer lineNumA, Integer lineNumB, String content) { |
|
10 |
+ public DiffLine(FileDiff file, DiffLineType type, Integer lineNumA, Integer lineNumB, |
|
11 |
+ String content) { |
|
12 |
+ this.file = file; |
|
10 | 13 |
this.kind = type; |
11 | 14 |
this.numA = lineNumA; |
12 | 15 |
this.numB = lineNumB; |
13 | 16 |
this.content = content; |
14 | 17 |
} |
15 |
-} |
|
18 |
+}(No newline at end of file) |
--- app/playRepository/FileDiff.java
+++ app/playRepository/FileDiff.java
... | ... | @@ -54,18 +54,18 @@ |
54 | 54 |
|
55 | 55 |
while (aCur < aEnd || bCur < bEnd) { |
56 | 56 |
if (aCur < curEdit.getBeginA() || endIdx + 1 < curIdx) { |
57 |
- hunk.lines.add(new DiffLine(DiffLineType.CONTEXT, aCur, bCur, |
|
57 |
+ hunk.lines.add(new DiffLine(this, DiffLineType.CONTEXT, aCur, bCur, |
|
58 | 58 |
a.getString(aCur))); |
59 | 59 |
isEndOfLineMissing = checkEndOfLineMissing(a, aCur); |
60 | 60 |
aCur++; |
61 | 61 |
bCur++; |
62 | 62 |
} else if (aCur < curEdit.getEndA()) { |
63 |
- hunk.lines.add(new DiffLine(DiffLineType.REMOVE, aCur, bCur, |
|
63 |
+ hunk.lines.add(new DiffLine(this, DiffLineType.REMOVE, aCur, bCur, |
|
64 | 64 |
a.getString(aCur))); |
65 | 65 |
isEndOfLineMissing = checkEndOfLineMissing(a, aCur); |
66 | 66 |
aCur++; |
67 | 67 |
} else if (bCur < curEdit.getEndB()) { |
68 |
- hunk.lines.add(new DiffLine(DiffLineType.ADD, aCur, bCur, |
|
68 |
+ hunk.lines.add(new DiffLine(this, DiffLineType.ADD, aCur, bCur, |
|
69 | 69 |
b.getString(bCur))); |
70 | 70 |
isEndOfLineMissing = checkEndOfLineMissing(a, aCur); |
71 | 71 |
bCur++; |
--- app/playRepository/GitRepository.java
+++ app/playRepository/GitRepository.java
... | ... | @@ -1509,8 +1509,8 @@ |
1509 | 1509 |
|
1510 | 1510 |
for (DiffEntry diff : formatter.scan(treeParserA, treeParserB)) { |
1511 | 1511 |
FileDiff fileDiff = new FileDiff(); |
1512 |
- fileDiff.commitA = commitA.getName(); |
|
1513 |
- fileDiff.commitB = commitB.getName(); |
|
1512 |
+ fileDiff.commitA = commitA != null ? commitA.getName() : null; |
|
1513 |
+ fileDiff.commitB = commitB != null ? commitB.getName() : null; |
|
1514 | 1514 |
|
1515 | 1515 |
fileDiff.changeType = diff.getChangeType(); |
1516 | 1516 |
|
--- app/utils/TemplateHelper.scala
+++ app/utils/TemplateHelper.scala
... | ... | @@ -7,6 +7,17 @@ |
7 | 7 |
import java.security.MessageDigest |
8 | 8 |
import views.html._ |
9 | 9 |
import java.net.URI |
10 |
+import name.fraser.neil.plaintext.DiffMatchPatch |
|
11 |
+import DiffMatchPatch.Diff |
|
12 |
+import DiffMatchPatch.Operation._ |
|
13 |
+import playRepository.DiffLine |
|
14 |
+import playRepository.DiffLineType |
|
15 |
+import models.CodeComment |
|
16 |
+import scala.collection.JavaConversions._ |
|
17 |
+import org.apache.commons.lang3.StringEscapeUtils.escapeHtml4 |
|
18 |
+import views.html.partial_diff_comment_on_line |
|
19 |
+import views.html.partial_diff_line |
|
20 |
+import name.fraser.neil.plaintext.DiffMatchPatch |
|
10 | 21 |
|
11 | 22 |
object TemplateHelper { |
12 | 23 |
|
... | ... | @@ -90,4 +101,92 @@ |
90 | 101 |
case (_) => defaultURI |
91 | 102 |
} |
92 | 103 |
} |
104 |
+ |
|
105 |
+ object DiffRenderer { |
|
106 |
+ |
|
107 |
+ def removedWord(word: String) = "<span class='remove'>" + word + "</span>" |
|
108 |
+ |
|
109 |
+ def addedWord(word: String) = "<span class='add'>" + word + "</span>" |
|
110 |
+ |
|
111 |
+ def mergeList(a: List[String], b: List[String]) = { |
|
112 |
+ a.zip(b).map(v => v._1 + v._2) |
|
113 |
+ } |
|
114 |
+ |
|
115 |
+ def wordDiffLinesInHtml(diffList: List[Diff]): List[String] = |
|
116 |
+ diffList match { |
|
117 |
+ case Nil => List("", "") |
|
118 |
+ case head :: tail => mergeList(wordDiffLineInHtml(head), wordDiffLinesInHtml(tail)) |
|
119 |
+ } |
|
120 |
+ |
|
121 |
+ def wordDiffLineInHtml(diff: Diff) = |
|
122 |
+ diff.operation match { |
|
123 |
+ case DELETE => List(removedWord(diff.text), "") |
|
124 |
+ case INSERT => List("", addedWord(diff.text)) |
|
125 |
+ case _ => List(diff.text, diff.text) |
|
126 |
+ } |
|
127 |
+ |
|
128 |
+ /* |
|
129 |
+ def writeHtmlLine(klass: String, indicator: String, numA: Integer, numB: Integer, html: String, commentsOnLine: List[_ <: CodeComment]) = { |
|
130 |
+ partial_diff_line_html(klass, indicator, numA, numB, html) + (if(commentsOnLine != null) partial_diff_comment_on_line(commentsOnLine).body else "") |
|
131 |
+ } |
|
132 |
+ |
|
133 |
+ def renderWordDiff(lineA: DiffLine, lineB: DiffLine, comments: Map[String, List[_ <: CodeComment]]) = { |
|
134 |
+ val lines = wordDiffLinesInHtml((new DiffMatchPatch()).diffMain(lineA.content, lineB.content).toList) |
|
135 |
+ writeHtmlLine(lineA.kind.toString.toLowerCase, "-", null, lineA.numA + 1, lines(0), commentsOrEmpty(comments, commentKey(lineA.file.pathA, "remove", lineA.numA + 1))) + writeHtmlLine(lineB.kind.toString.toLowerCase, "+", lineB.numB + 1, null, lines(1), commentsOrEmpty(comments, commentKey(lineB.file.pathB, "add", lineB.numB + 1))) |
|
136 |
+ } |
|
137 |
+ */ |
|
138 |
+ |
|
139 |
+ /* Not implemented yet */ |
|
140 |
+ def renderWordDiff(lineA: DiffLine, lineB: DiffLine, comments: Map[String, List[CodeComment]]) = |
|
141 |
+ renderLine(lineA, comments) + renderLine(lineB, comments) |
|
142 |
+ |
|
143 |
+ def renderTwoLines(lineA: DiffLine, lineB: DiffLine, comments: Map[String, List[CodeComment]]) = |
|
144 |
+ (lineA.kind, lineB.kind) match { |
|
145 |
+ case (DiffLineType.REMOVE, DiffLineType.ADD) => renderWordDiff(lineA, lineB, comments) |
|
146 |
+ case _ => renderLine(lineA, comments) + renderLine(lineB, comments) |
|
147 |
+ } |
|
148 |
+ |
|
149 |
+ def commentKey(path: String, side: String, lineNum: Integer) = |
|
150 |
+ path + ":" + side + ":" + lineNum |
|
151 |
+ |
|
152 |
+ def commentsOrEmpty(comments: Map[String, List[CodeComment]], key: String) = |
|
153 |
+ if (comments.contains(key)) comments(key) else Nil |
|
154 |
+ |
|
155 |
+ def commentsOnAddLine(line: DiffLine, comments: Map[String, List[CodeComment]]) = |
|
156 |
+ commentsOrEmpty(comments, commentKey(line.file.pathB, "add", line.numB + 1)) |
|
157 |
+ |
|
158 |
+ def commentsOnRemoveLine(line: DiffLine, comments: Map[String, List[CodeComment]]) = |
|
159 |
+ commentsOrEmpty(comments, commentKey(line.file.pathA, "remove", line.numA + 1)) |
|
160 |
+ |
|
161 |
+ def commentsOnContextLine(line: DiffLine, comments: Map[String, List[CodeComment]]) = |
|
162 |
+ commentsOrEmpty(comments, commentKey(line.file.pathB, "context", line.numB + 1)) |
|
163 |
+ |
|
164 |
+ def indicator(line: DiffLine) = |
|
165 |
+ line.kind match { |
|
166 |
+ case DiffLineType.ADD => "+" |
|
167 |
+ case DiffLineType.REMOVE => "-" |
|
168 |
+ case _ => " " |
|
169 |
+ } |
|
170 |
+ |
|
171 |
+ def renderLine(line: DiffLine, num: Integer, numA: Integer, numB: Integer, commentsOnLine: List[CodeComment]) = |
|
172 |
+ partial_diff_line(line.kind.toString.toLowerCase, indicator(line), num, numA, numB, line.content) + |
|
173 |
+ partial_diff_comment_on_line(commentsOnLine).body.trim |
|
174 |
+ |
|
175 |
+ def renderLine(line: DiffLine, comments: Map[String, List[CodeComment]]): String = |
|
176 |
+ line.kind match { |
|
177 |
+ case DiffLineType.ADD => |
|
178 |
+ renderLine(line, line.numB + 1, null, line.numB + 1, commentsOnAddLine(line, comments)) |
|
179 |
+ case DiffLineType.REMOVE => |
|
180 |
+ renderLine(line, line.numA + 1, line.numA + 1, null, commentsOnRemoveLine(line, comments)) |
|
181 |
+ case _ => |
|
182 |
+ renderLine(line, line.numB + 1, line.numA + 1, line.numB + 1, commentsOnContextLine(line, comments)) |
|
183 |
+ } |
|
184 |
+ |
|
185 |
+ def renderLines(lines: List[DiffLine], comments: Map[String, List[CodeComment]]): String = |
|
186 |
+ lines match { |
|
187 |
+ case Nil => "" |
|
188 |
+ case first::Nil => renderLine(first, comments) |
|
189 |
+ case first::second::tail => renderTwoLines(first, second, comments) + renderLines(tail, comments) |
|
190 |
+ } |
|
191 |
+ } |
|
93 | 192 |
} |
--- app/views/code/diff.scala.html
+++ app/views/code/diff.scala.html
... | ... | @@ -1,4 +1,4 @@ |
1 |
-@(project: Project, commit:playRepository.Commit, parentCommit:playRepository.Commit, patch: String, comments:List[CommitComment], selectedBranch:String, diff: List[playRepository.FileDiff]) |
|
1 |
+@(project: Project, commit:playRepository.Commit, parentCommit:playRepository.Commit, comments:List[CommitComment], selectedBranch:String, diff: List[playRepository.FileDiff]) |
|
2 | 2 |
|
3 | 3 |
@import playRepository.RepositoryService |
4 | 4 |
@import java.net.URLEncoder |
... | ... | @@ -90,8 +90,9 @@ |
90 | 90 |
} |
91 | 91 |
</p> |
92 | 92 |
<pre class="commitMsg">@commit.getMessage</pre> |
93 |
- |
|
93 |
+ <div class="diff-body"> |
|
94 | 94 |
@views.html.partial_diff(diff, comments) |
95 |
+ </div> |
|
95 | 96 |
|
96 | 97 |
<div id="compare" class="modal hide compare-wrap" tabindex="-1" role="dialog"> |
97 | 98 |
<h4 class="path"> |
... | ... | @@ -107,6 +108,7 @@ |
107 | 108 |
|
108 | 109 |
@** Comment **@ |
109 | 110 |
<div class="board-comment-wrap"> |
111 |
+ @defining(comments.filter(v => v.line == null)) { comments => |
|
110 | 112 |
@if(comments.size > 0) { |
111 | 113 |
<ul class="comments"> |
112 | 114 |
@for(comment <- comments){ |
... | ... | @@ -140,6 +142,7 @@ |
140 | 142 |
} |
141 | 143 |
</ul> |
142 | 144 |
} |
145 |
+ } |
|
143 | 146 |
|
144 | 147 |
@common.commentForm(project, ResourceType.COMMIT_COMMENT, routes.CodeHistoryApp.newComment(project.owner, project.name, commit.getId).toString()) |
145 | 148 |
</div> |
... | ... | @@ -159,16 +162,6 @@ |
159 | 162 |
</div> |
160 | 163 |
|
161 | 164 |
@common.markdown(project) |
162 |
- |
|
163 |
-<script type="text/x-jquery-tmpl" id="comment-icon-template"> |
|
164 |
- <i class="yobicon-comments"></i> |
|
165 |
-</script> |
|
166 |
-<script type="text/x-jquery-tmpl" id="linenum-column-template"> |
|
167 |
- <td class="linenum"></td> |
|
168 |
-</script> |
|
169 |
-<script type="text/x-jquery-tmpl" id="comment-button-template"> |
|
170 |
- <button class="ybtn medium btn-thread"></button> |
|
171 |
-</script> |
|
172 | 165 |
|
173 | 166 |
<link rel="stylesheet" type="text/css" media="screen" href="@routes.Assets.at("javascripts/lib/mergely/codemirror.css")"> |
174 | 167 |
<link rel="stylesheet" type="text/css" media="screen" href="@routes.Assets.at("javascripts/lib/mergely/mergely.css")"> |
--- app/views/error/notfound.scala.html
+++ app/views/error/notfound.scala.html
... | ... | @@ -33,4 +33,4 @@ |
33 | 33 |
<a href="@getReturnURL(targetType)" class="ybtn ybtn-primary">@Messages("button.list")</a> |
34 | 34 |
</div> |
35 | 35 |
</div> |
36 |
-}(No newline at end of file) |
|
36 |
+} |
--- app/views/git/diff.scala.html
+++ app/views/git/diff.scala.html
... | ... | @@ -1,4 +1,4 @@ |
1 |
-@(pull: PullRequest, commit:playRepository.Commit, parentCommit:playRepository.Commit, patch: String, comments:List[CommitComment]) |
|
1 |
+@(pull: PullRequest, commit:playRepository.Commit, parentCommit:playRepository.Commit, diff: List[playRepository.FileDiff], comments:List[CommitComment]) |
|
2 | 2 |
|
3 | 3 |
@import playRepository.RepositoryService |
4 | 4 |
@import java.net.URLEncoder |
... | ... | @@ -73,9 +73,8 @@ |
73 | 73 |
} |
74 | 74 |
</p> |
75 | 75 |
<pre class="commitMsg">@commit.getMessage</pre> |
76 |
- |
|
77 |
- <div class="diff-wrap"> |
|
78 |
- <div id="commit" class="diff-body show-comments">@patch</div> |
|
76 |
+ <div class="diff-body"> |
|
77 |
+ @views.html.partial_diff(diff, comments) |
|
79 | 78 |
</div> |
80 | 79 |
<div id="compare" class="modal hide compare-wrap" tabindex="-1" role="dialog"> |
81 | 80 |
<h4 class="path"> |
... | ... | @@ -92,6 +91,7 @@ |
92 | 91 |
|
93 | 92 |
@** Comment **@ |
94 | 93 |
<div class="board-comment-wrap"> |
94 |
+ @defining(comments.filter(v => v.line == null)) { comments => |
|
95 | 95 |
@if(comments.size > 0) { |
96 | 96 |
<ul class="comments"> |
97 | 97 |
@for(comment <- comments){ |
... | ... | @@ -125,6 +125,7 @@ |
125 | 125 |
} |
126 | 126 |
</ul> |
127 | 127 |
} |
128 |
+ } |
|
128 | 129 |
|
129 | 130 |
@common.commentForm(pull.fromProject, ResourceType.COMMIT_COMMENT, routes.CodeHistoryApp.newComment(pull.fromProject.owner, pull.fromProject.name, commit.getId).toString()) |
130 | 131 |
</div> |
--- app/views/git/view.scala.html
+++ app/views/git/view.scala.html
... | ... | @@ -393,7 +393,7 @@ |
393 | 393 |
</div> |
394 | 394 |
|
395 | 395 |
<div id="__changes" class="tab-pane @if(activeTab == "changes"){ active }"> |
396 |
- <div id="pull-request-changes" class="diff-body show-comments"> |
|
396 |
+ <div class="diff-body"> |
|
397 | 397 |
@views.html.partial_diff(pull.getDiff, comments) |
398 | 398 |
</div> |
399 | 399 |
</div> |
... | ... | @@ -431,6 +431,12 @@ |
431 | 431 |
event.preventDefault(); |
432 | 432 |
$("#outdatedCommits").toggle(); |
433 | 433 |
}) |
434 |
+ |
|
435 |
+ $yobi.loadModule("code.Diff", { |
|
436 |
+ "welDiff": $("#pull-request-changes"), |
|
437 |
+ "sAttachmentAction": "@routes.AttachmentApp.uploadFile", |
|
438 |
+ "bCommentable": "isProjectResourceCreatable(UserApp.currentUser, project, ResourceType.PULL_REQUEST_COMMENT)" |
|
439 |
+ }); |
|
434 | 440 |
}); |
435 | 441 |
</script> |
436 | 442 |
} |
--- app/views/partial_diff.scala.html
+++ app/views/partial_diff.scala.html
... | ... | @@ -1,127 +1,69 @@ |
1 | 1 |
@(fileDiffs: java.util.List[playRepository.FileDiff], comments:java.util.List[_ <: CodeComment]) |
2 | 2 |
|
3 | 3 |
@import playRepository.DiffLineType |
4 |
+@import playRepository.DiffLine |
|
5 |
+@import playRepository.FileDiff |
|
4 | 6 |
@import org.eclipse.jgit.diff.DiffEntry |
7 |
+@import org.eclipse.jgit.diff.RawText |
|
8 |
+@import utils.TemplateHelper.DiffRenderer._ |
|
9 |
+@import utils.TemplateHelper._ |
|
10 |
+@import scala.collection.immutable.Map |
|
11 |
+@import scala.collection.immutable.List |
|
5 | 12 |
@import scala.collection.JavaConversions._ |
6 | 13 |
|
7 |
-@render(fileDiffs, comments.toList.groupBy((comment: CodeComment) => commentKey(comment.path, comment.side, comment.line))) |
|
8 |
- |
|
9 |
-@writeLine(klass: String, prefix: String, numA: Integer, numB: Integer, content: String, commentsOnLine: scala.collection.immutable.List[_ <: CodeComment]) = { |
|
10 |
- <tr class="@klass" data-line="@Option(numA).getOrElse(numB)" data-type="@klass"><td class="linenum"><i class="icon-comment"></i>@Option(numA).getOrElse("")</td><td class="linenum">@Option(numB).getOrElse("")</td><td><span>@prefix</span>@content</td></tr> |
|
11 |
- @if(commentsOnLine != null) { @writeCommentsOnLine(commentsOnLine) } |
|
12 |
-} |
|
13 |
- |
|
14 |
-@writeCommentsOnLine(comments: scala.collection.immutable.List[CodeComment]) = { |
|
15 |
- <tr class="comments board-comment-wrap"> |
|
16 |
- <td colspan=3> |
|
17 |
- <ul class="comments"> |
|
18 |
- @for(comment: CodeComment <- comments) { |
|
19 |
- <li id="comment-@comment.id" data-path="@comment.path" data-side="@comment.side" data-line="@comment.line" class="comment"> |
|
20 |
- <div class="comment-avatar"> |
|
21 |
- <a href="@routes.UserApp.userInfo(comment.authorLoginId)" class="avatar-wrap" data-toggle="tooltip" data-placement="top" title="@comment.authorName"> |
|
22 |
- <img src="@User.findByLoginId(comment.authorLoginId).avatarUrl" width="32" height="32" alt="@comment.authorLoginId"> |
|
23 |
- </a> |
|
24 |
- </div> |
|
25 |
- <div class="media-body"> |
|
26 |
- <div class="meta-info"> |
|
27 |
- <span class="comment_author pull-left"> |
|
28 |
- <i class="yobicon-comment"></i> |
|
29 |
- <a href="@routes.UserApp.userInfo(comment.authorLoginId)" data-toggle="tooltip" data-placement="top" title="@comment.authorName"> |
|
30 |
- <strong>@comment.authorLoginId </strong> |
|
31 |
- </a> |
|
32 |
- </span> |
|
33 |
- <span class="ago"><a href="#comment-@comment.id">@utils.TemplateHelper.agoString(utils.JodaDateUtil.ago(comment.createdDate))</a></span> |
|
34 |
- @if(utils.AccessControl.isAllowed(UserApp.currentUser(), comment.asResource(), Operation.DELETE)){ |
|
35 |
- <span class="edit pull-right"> |
|
36 |
- <!-- FIXME: Delete comment? pull request하고 commit comment 구분해야 함 --> |
|
37 |
- <button class="btn-transparent pull-right close" data-request-method="delete" data-request-uri="@routes.CommentApp.delete(comment.asResource.getType.resource, comment.asResource.getId)"><i class="yobicon-trash"></i></button> |
|
38 |
- </span> |
|
39 |
- } |
|
40 |
- </div> |
|
41 |
- |
|
42 |
- <div class="comment-body markdown-wrap markdown-before" markdown="true">@comment.contents</div> |
|
43 |
- |
|
44 |
- <div class="attachments" resourceType="@comment.asResource.getType" resourceId="@comment.id"></div> |
|
45 |
- </div> |
|
46 |
- </li> |
|
47 |
- } |
|
48 |
- <button class="nbtn medium btn-thread">댓글창 열기</button> |
|
49 |
- <button class="nbtn medium btn-thread" style="display: none;">댓글창 닫기</button> |
|
50 |
- </ul> |
|
51 |
- </td> |
|
52 |
- </tr> |
|
53 |
-} |
|
54 |
- |
|
55 |
-@commentKey(path: String, side: String, lineNum: Integer) = @{ path + ":" + side + ":" + lineNum } |
|
56 |
- |
|
57 |
-@commentsOrNull(comments: Map[String, scala.collection.immutable.List[_ <: CodeComment]], key: String) = @{ |
|
58 |
- if(comments.contains(key)) { |
|
59 |
- comments(key) |
|
60 |
- } else { |
|
61 |
- null |
|
62 |
- } |
|
63 |
-} |
|
64 |
- |
|
65 |
-@renderFileDiff(diff: playRepository.FileDiff, comments:scala.collection.immutable.Map[String, scala.collection.immutable.List[_ <: CodeComment]]) = { |
|
14 |
+@renderDiffLines(diff: playRepository.FileDiff, comments: Map[String, List[_ <: CodeComment]]) = { |
|
15 |
+ @if(diff.getHunks.size > 0){ |
|
66 | 16 |
@for(hunk <- diff.getHunks) { |
67 |
- <tr class="range"><td>...</td><td>...</td><td>-@(hunk.beginA + 1),@(hunk.endA - hunk.beginA) +@(hunk.beginB + 1),@(hunk.endB - hunk.beginB)</td></tr> |
|
68 |
- @for(line <- hunk.lines){ |
|
69 |
- @line.kind match { |
|
70 |
- case DiffLineType.ADD => { @writeLine(line.kind.toString.toLowerCase, "+", null, line.numB + 1, line.content, commentsOrNull(comments, commentKey(diff.pathB, "add", line.numB + 1))) } |
|
71 |
- case DiffLineType.REMOVE => { @writeLine(line.kind.toString.toLowerCase, "-", line.numA + 1, null, line.content, commentsOrNull(comments, commentKey(diff.pathA, "remove", line.numA + 1))) } |
|
72 |
- case _ => { @writeLine(line.kind.toString.toLowerCase, " ", line.numA + 1, line.numB + 1, line.content, commentsOrNull(comments, commentKey(diff.pathA, "base", line.numA + 1))) } |
|
73 |
- } |
|
74 |
- } |
|
17 |
+ <tr class="range"><td>...</td><td>...</td><td>-@(hunk.beginA + 1),@(hunk.endA - hunk.beginA) +@(hunk.beginB + 1),@(hunk.endB - hunk.beginB)</td></tr> |
|
18 |
+ @Html(renderLines(hunk.lines.toList, comments)) |
|
75 | 19 |
} |
20 |
+ } else { |
|
21 |
+ <tr><td colspan=3>@Messages("code.noChanges")</td></tr> |
|
22 |
+ } |
|
76 | 23 |
} |
77 | 24 |
|
78 |
-@render(fileDiffs: java.util.List[playRepository.FileDiff], comments:scala.collection.immutable.Map[String, scala.collection.immutable.List[_ <: CodeComment]]) = { |
|
79 |
-@for(diff <- fileDiffs) { |
|
25 |
+@renderRawFile(path: String, rawText: RawText, kind: String, prefix: String, comments: Map[String, List[_ <: CodeComment]]) = { |
|
26 |
+ @for(i <- 0 until rawText.size) { |
|
27 |
+ @partial_diff_line(kind, prefix, null, i + 1, rawText.getString(i)) |
|
28 |
+ @partial_diff_comment_on_line(commentsOrEmpty(comments, path + ":" + kind + ":" + (i + 1))) |
|
29 |
+ } |
|
30 |
+} |
|
31 |
+ |
|
32 |
+@renderAddedLines(rawText: RawText, path: String, comments:Map[String, List[_ <: CodeComment]]) = { |
|
33 |
+ @renderRawFile(path, rawText, "add", "+", comments) |
|
34 |
+} |
|
35 |
+ |
|
36 |
+@renderRemovedLines(rawText: RawText, path: String, comments:Map[String, List[_ <: CodeComment]]) = { |
|
37 |
+ @renderRawFile(path, rawText, "remove", "-", comments) |
|
38 |
+} |
|
39 |
+ |
|
40 |
+@renderFile(path: String, fileHeader: String, renderedLines: Html) = { |
|
41 |
+ <table class="diff-body show-comments" data-path="@path"><tbody> |
|
42 |
+ <tr class="file" id="@path.substring(1)"> |
|
43 |
+ <td class="linenum"></td><td class="linenum"></td><td><span>@fileHeader</span></td> |
|
44 |
+ </tr> |
|
45 |
+ @renderedLines |
|
46 |
+ </tbody></table> |
|
47 |
+} |
|
48 |
+ |
|
49 |
+@defining(comments.toList.groupBy((comment: CodeComment) => commentKey(comment.path, comment.side, comment.line))) { comments => |
|
50 |
+ @for(diff <- fileDiffs) { |
|
80 | 51 |
@diff.changeType match { |
81 |
- case DiffEntry.ChangeType.MODIFY => { |
|
82 |
- <table class="diff-body show-comments" data-path="@diff.pathB"><tbody> |
|
83 |
- <tr class="file"> |
|
84 |
- <td colspan=3>@diff.pathB</td> |
|
85 |
- </tr> |
|
86 |
- @renderFileDiff(diff, comments) |
|
87 |
- </tbody></table> |
|
52 |
+ case DiffEntry.ChangeType.MODIFY => { |
|
53 |
+ @renderFile(diff.pathB, diff.pathB, renderDiffLines(diff, comments)) |
|
54 |
+ } |
|
55 |
+ case DiffEntry.ChangeType.ADD => { |
|
56 |
+ @renderFile(diff.pathB, Messages("code.addedPath", diff.pathB), renderAddedLines(diff.b, diff.pathB, comments)) |
|
57 |
+ } |
|
58 |
+ case DiffEntry.ChangeType.DELETE => { |
|
59 |
+ @renderFile(diff.pathA, Messages("code.deletedPath", diff.pathA), renderRemovedLines(diff.a, diff.pathA, comments)) |
|
60 |
+ } |
|
61 |
+ case DiffEntry.ChangeType.RENAME => { |
|
62 |
+ @renderFile(diff.pathB, Messages("code.renamedPath", diff.pathA, diff.pathB), renderDiffLines(diff, comments)) |
|
63 |
+ } |
|
64 |
+ case DiffEntry.ChangeType.COPY => { |
|
65 |
+ @renderFile(diff.pathB, Messages("code.copiedPath", diff.pathA, diff.pathA), renderAddedLines(diff.b, diff.pathB, comments)) |
|
66 |
+ } |
|
88 | 67 |
} |
89 |
- |
|
90 |
- case DiffEntry.ChangeType.ADD => { |
|
91 |
- <table class="diff-body show-comments" data-path="@diff.pathB"><tbody> |
|
92 |
- <tr class="file"> |
|
93 |
- <td colspan=3>@diff.pathB (Added)</td> |
|
94 |
- </tr> |
|
95 |
- @for(i <- 0 until diff.b.size) { |
|
96 |
- @writeLine("add", "+", null, i + 1, diff.b.getString(i), commentsOrNull(comments, diff.pathB + ":add:" + (i + 1))) |
|
97 |
- } |
|
98 |
- </tbody></table> |
|
99 |
- } |
|
100 |
- case DiffEntry.ChangeType.DELETE => { |
|
101 |
- <table class="diff-body show-comments" data-path="@diff.pathA"><tbody> |
|
102 |
- <tr class="file"> |
|
103 |
- <td colspan=3>@diff.pathA (Deleted)</td> |
|
104 |
- </tr> |
|
105 |
- @for(i <- 0 until diff.a.size) { |
|
106 |
- @writeLine("remove", "-", null, i + 1, diff.a.getString(i), commentsOrNull(comments, diff.pathA + ":add:" + (i + 1))) |
|
107 |
- } |
|
108 |
- </tbody></table> |
|
109 |
- } |
|
110 |
- case DiffEntry.ChangeType.RENAME => { |
|
111 |
- <table class="diff-body show-comments" data-path="@diff.pathB"><tbody> |
|
112 |
- <tr class="file"> |
|
113 |
- <td colspan=3>@diff.pathB -> @diff.pathB</td> |
|
114 |
- </tr> |
|
115 |
- @renderFileDiff(diff, comments) |
|
116 |
- </tbody></table> |
|
117 |
- } |
|
118 |
- case DiffEntry.ChangeType.COPY => { |
|
119 |
- <table class="diff-body show-comments" data-path="@diff.pathB"><tbody> |
|
120 |
- <tr class="file"> |
|
121 |
- <td colspan=3>Copy @diff.pathA to @diff.pathB</td> |
|
122 |
- </tr> |
|
123 |
- </tbody></table> |
|
124 |
- } |
|
125 |
- } |
|
126 |
-} |
|
68 |
+ } |
|
127 | 69 |
} |
+++ app/views/partial_diff_comment_on_line.scala.html
... | ... | @@ -0,0 +1,44 @@ |
1 | +@(comments: List[CodeComment]) | |
2 | + | |
3 | +@import utils.TemplateHelper._ | |
4 | +@import utils.TemplateHelper.DiffRenderer._ | |
5 | + | |
6 | +@if(!comments.isEmpty){ | |
7 | +<tr class="comments board-comment-wrap" data-commit-id="@comments(0).commitId"> | |
8 | + <td colspan=3> | |
9 | + <ul class="comments"> | |
10 | + @for(comment: CodeComment <- comments) { | |
11 | + <li id="comment-@comment.id" data-path="@comment.path" data-side="@comment.side" data-line="@comment.line" class="comment"> | |
12 | + <div class="comment-avatar"> | |
13 | + <a href="@routes.UserApp.userInfo(comment.authorLoginId)" class="avatar-wrap" data-toggle="tooltip" data-placement="top" title="@comment.authorName"> | |
14 | + <img src="@User.findByLoginId(comment.authorLoginId).avatarUrl" width="32" height="32" alt="@comment.authorLoginId"> | |
15 | + </a> | |
16 | + </div> | |
17 | + <div class="media-body"> | |
18 | + <div class="meta-info"> | |
19 | + <span class="comment_author pull-left"> | |
20 | + <i class="yobicon-comment"></i> | |
21 | + <a href="@routes.UserApp.userInfo(comment.authorLoginId)" data-toggle="tooltip" data-placement="top" title="@comment.authorName"> | |
22 | + <strong>@comment.authorLoginId </strong> | |
23 | + </a> | |
24 | + </span> | |
25 | + <span class="ago"><a href="#comment-@comment.id">@utils.TemplateHelper.agoString(utils.JodaDateUtil.ago(comment.createdDate))</a></span> | |
26 | + @if(utils.AccessControl.isAllowed(UserApp.currentUser(), comment.asResource(), Operation.DELETE)){ | |
27 | + <span class="edit pull-right"> | |
28 | + <button class="btn-transparent pull-right close" data-request-method="delete" data-request-uri="@routes.CommentApp.delete(comment.asResource.getType.resource, comment.asResource.getId)"><i class="yobicon-trash"></i></button> | |
29 | + </span> | |
30 | + } | |
31 | + </div> | |
32 | + | |
33 | + <div class="comment-body markdown-wrap markdown-before" markdown="true">@comment.contents</div> | |
34 | + | |
35 | + <div class="attachments" resourceType="@comment.asResource.getType" resourceId="@comment.id"></div> | |
36 | + </div> | |
37 | + </li> | |
38 | + } | |
39 | + <button class="nbtn medium btn-thread open-comment-box">@Messages("code.openCommentBox")</button> | |
40 | + <button class="nbtn medium btn-thread close-comment-box" style="display: none;">@Messages("code.closeCommentBox")</button> | |
41 | + </ul> | |
42 | + </td> | |
43 | +</tr> | |
44 | +} |
+++ app/views/partial_diff_line.scala.html
... | ... | @@ -0,0 +1,3 @@ |
1 | +@(klass: String, prefix: String, num: Integer, numA: Integer, numB: Integer, content: String) | |
2 | + | |
3 | +<tr class="@klass" data-line="@num" data-type="@klass"><td class="linenum"><i class="icon-comment"></i>@Option(numA).getOrElse("")</td><td class="linenum">@Option(numB).getOrElse("")</td><td class="code"><span>@prefix</span>@content</td></tr> |
--- conf/messages.en
+++ conf/messages.en
... | ... | @@ -62,6 +62,10 @@ |
62 | 62 |
button.user.makeAccountUnlock.false = Lock |
63 | 63 |
button.user.makeAccountUnlock.true = Unlock |
64 | 64 |
button.yes = Yes |
65 |
+code.addedPath = {0} (Added) |
|
66 |
+code.deletedPath = {0} (Deleted) |
|
67 |
+code.renamedPath = {1} (Renamed from {0}) |
|
68 |
+code.copiedPath = {1} (Copied from {0}) |
|
65 | 69 |
code.author = Author |
66 | 70 |
code.closeCommentBox = Close Comment Box |
67 | 71 |
code.commitDate = Commit Date |
... | ... | @@ -72,6 +76,7 @@ |
72 | 76 |
code.files = Files |
73 | 77 |
code.history = History |
74 | 78 |
code.newer = Newer |
79 |
+code.noChanges = No changes |
|
75 | 80 |
code.nohead = <div class="alert alert-block"><h4>The repository is empty!</h4></div> |
76 | 81 |
code.nohead.clone = Create new local repository by cloning the repository created on {0}, and push README.md file. |
77 | 82 |
code.nohead.init = Or, create new local repository by yourself and add a remote that indicate the {0} repository and push README.md file. |
--- conf/messages.ko
+++ conf/messages.ko
... | ... | @@ -62,6 +62,10 @@ |
62 | 62 |
button.user.makeAccountUnlock.false = 계정잠그기 |
63 | 63 |
button.user.makeAccountUnlock.true = 잠김해제 |
64 | 64 |
button.yes = 예 |
65 |
+code.addedPath = {0} (추가됨) |
|
66 |
+code.deletedPath = {0} (삭제됨) |
|
67 |
+code.renamedPath = {1} ({0}로부터 이름 변경됨) |
|
68 |
+code.copiedPath = {1} ({0}로부터 복사됨) |
|
65 | 69 |
code.author = 작성자 |
66 | 70 |
code.closeCommentBox = 댓글창 닫기 |
67 | 71 |
code.commitDate = 커밋한 날짜 |
... | ... | @@ -72,6 +76,7 @@ |
72 | 76 |
code.files = 파일 |
73 | 77 |
code.history = 변경이력 |
74 | 78 |
code.newer = 이전 |
79 |
+code.noChanges = 변경 없음 |
|
75 | 80 |
code.nohead = <div class="alert alert-block"><h4>저장소가 비어있습니다!</h4></div> |
76 | 81 |
code.nohead.clone = {0}에 생성된 Git 저장소를 clone 받아서 로컬에 Git 저장소를 만들고 README.md 파일을 {0}에 올릴 수 있습니다. |
77 | 82 |
code.nohead.init = 또는, 로컬에서 Git 저장소를 만들고 remote로 {0}에 생성된 Git 저장소를 직접 추가하고 README.md 파일을 {0}에 올릴 수 있습니다. |
--- public/javascripts/service/yobi.code.Diff.js
+++ public/javascripts/service/yobi.code.Diff.js
... | ... | @@ -48,9 +48,6 @@ |
48 | 48 |
// 미니맵 |
49 | 49 |
htVar.sQueryMiniMap = htOptions.sQueryMiniMap || "li.comment"; |
50 | 50 |
htVar.sTplMiniMapLink = '<a href="#${id}" style="top:${top}px; height:${height}px;"></a>'; |
51 |
- |
|
52 |
- htVar.sCommitA = htOptions.sCommitA; |
|
53 |
- htVar.sCommitB = htOptions.sCommitB; |
|
54 | 51 |
} |
55 | 52 |
|
56 | 53 |
/** |
... | ... | @@ -65,14 +62,9 @@ |
65 | 62 |
.append(welHidden.clone().attr('name', 'line')) |
66 | 63 |
.append(welHidden.clone().attr('name', 'side')) |
67 | 64 |
.append(welHidden.clone().attr('name', 'commitA')) |
68 |
- .append(welHidden.clone().attr('name', 'commitB')); |
|
65 |
+ .append(welHidden.clone().attr('name', 'commitB')) |
|
66 |
+ .append(welHidden.clone().attr('name', 'commitId')); |
|
69 | 67 |
htElement.welComments = $('ul.comments'); |
70 |
- |
|
71 |
- if (htVar.bCommentable) { |
|
72 |
- htElement.welIcon = $('#comment-icon-template').tmpl(); |
|
73 |
- } |
|
74 |
- htElement.welEmptyLineNumColumn = $('#linenum-column-template').tmpl(); |
|
75 |
- htElement.welEmptyCommentButton = $('#comment-button-template').tmpl(); |
|
76 | 68 |
|
77 | 69 |
// 지켜보기 |
78 | 70 |
htElement.welBtnWatch = $('#watch-button'); |
... | ... | @@ -114,7 +106,7 @@ |
114 | 106 |
$(window).on("resize", _initMiniMap); |
115 | 107 |
$(window).on("scroll", _updateMiniMapCurr); |
116 | 108 |
$(window).on("resize", _resizeMergely); |
117 |
- $('tr .linenum:first-child').click(_onClickLineNumA); |
|
109 |
+ $('div.diff-body[data-outdated!="true"] tr .linenum:first-child').click(_onClickLineNumA); |
|
118 | 110 |
|
119 | 111 |
_attachCommentBoxToggleEvent(); |
120 | 112 |
} |
... | ... | @@ -254,53 +246,6 @@ |
254 | 246 |
} |
255 | 247 |
} |
256 | 248 |
|
257 |
- /** |
|
258 |
- * welTr에 줄 번호를 붙인다. |
|
259 |
- * |
|
260 |
- * @param {Object} welTr |
|
261 |
- * @param {Number} nLineA |
|
262 |
- * @param {Number} nLineB |
|
263 |
- */ |
|
264 |
- function _prependLineNumberOnLine(welTr, nLineA, nLineB) { |
|
265 |
- var welLineNumA = |
|
266 |
- htElement.welEmptyLineNumColumn.clone().text(nLineA).addClass("linenum-from"); |
|
267 |
- var welLineNumB = |
|
268 |
- htElement.welEmptyLineNumColumn.clone().text(nLineB).addClass("linenum-to"); |
|
269 |
- |
|
270 |
- welTr.append(welLineNumA); |
|
271 |
- welTr.append(welLineNumB); |
|
272 |
- |
|
273 |
- if (htVar.bCommentable |
|
274 |
- && (!isNaN(parseInt(nLineA)) || !isNaN(parseInt(nLineB)))) { |
|
275 |
- _prependCommentIcon(welLineNumA, welTr); |
|
276 |
- welLineNumA.click(_onClickLineNumA); |
|
277 |
- } |
|
278 |
- } |
|
279 |
- |
|
280 |
- /** |
|
281 |
- * welPrependTo에, welHoverOn에 마우스 호버시 보여질 댓글 아이콘을 |
|
282 |
- * 붙인다. |
|
283 |
- * |
|
284 |
- * @param {Object} welPrependTo |
|
285 |
- * @param {Object} welHoverOn |
|
286 |
- */ |
|
287 |
- function _prependCommentIcon(welPrependTo, welHoverOn) { |
|
288 |
- var welIcon = htElement.welIcon.clone() |
|
289 |
- welIcon.prependTo(welPrependTo); |
|
290 |
- |
|
291 |
- welHoverOn.hover(function() { |
|
292 |
- welIcon.css('visibility', 'visible'); |
|
293 |
- }, function() { |
|
294 |
- welIcon.css('visibility', 'hidden'); |
|
295 |
- }); |
|
296 |
- |
|
297 |
- welPrependTo.hover(function() { |
|
298 |
- welIcon.css('opacity', '1.0'); |
|
299 |
- }, function() { |
|
300 |
- welIcon.css('opacity', '0.6'); |
|
301 |
- }); |
|
302 |
- } |
|
303 |
- |
|
304 | 249 |
function _attachCommentBoxToggleEvent() { |
305 | 250 |
if (htVar.bCommentable) { |
306 | 251 |
var welCloseButton = $('.close-comment-box'); |
... | ... | @@ -336,6 +281,7 @@ |
336 | 281 |
htElement.welEmptyCommentForm.find('[name=side]').removeAttr('value'); |
337 | 282 |
htElement.welEmptyCommentForm.find('[name=commitA]').removeAttr('value'); |
338 | 283 |
htElement.welEmptyCommentForm.find('[name=commitB]').removeAttr('value'); |
284 |
+ htElement.welEmptyCommentForm.find('[name=commitId]').removeAttr('value'); |
|
339 | 285 |
$('.code-browse-wrap').append(htElement.welEmptyCommentForm); |
340 | 286 |
_updateMiniMap(); |
341 | 287 |
} |
... | ... | @@ -379,7 +325,10 @@ |
379 | 325 |
var welCommentTr; |
380 | 326 |
var nLine = parseInt(welTr.data('line')); |
381 | 327 |
var sType = welTr.data('type'); |
382 |
- var sPath = welTr.closest('table').data('path'); |
|
328 |
+ var sCommitId; |
|
329 |
+ var sPath; |
|
330 |
+ var sCommitA = welTr.closest('div.diff-body').data('commitA'); |
|
331 |
+ var sCommitB = welTr.closest('div.diff-body').data('commitB'); |
|
383 | 332 |
|
384 | 333 |
if (isNaN(nLine)) { |
385 | 334 |
nLine = parseInt(welTr.prev().data('line')); |
... | ... | @@ -388,6 +337,14 @@ |
388 | 337 |
|
389 | 338 |
if (isNaN(nLine)) { |
390 | 339 |
return; |
340 |
+ } |
|
341 |
+ |
|
342 |
+ if (sType == 'remove') { |
|
343 |
+ sPath = welTr.closest('table').data('path-a'); |
|
344 |
+ sCommitId = sCommitA; |
|
345 |
+ } else { |
|
346 |
+ sPath = welTr.closest('table').data('path-b'); |
|
347 |
+ sCommitId = sCommitB; |
|
391 | 348 |
} |
392 | 349 |
|
393 | 350 |
if (htElement.welCommentTr) { |
... | ... | @@ -401,8 +358,10 @@ |
401 | 358 |
welCommentTr.find('[name=path]').attr('value', sPath); |
402 | 359 |
welCommentTr.find('[name=line]').attr('value', nLine); |
403 | 360 |
welCommentTr.find('[name=side]').attr('value', sType); |
404 |
- welCommentTr.find('[name=commitA]').attr('value', htVar.sCommitA); |
|
405 |
- welCommentTr.find('[name=commitB]').attr('value', htVar.sCommitB); |
|
361 |
+ |
|
362 |
+ welCommentTr.find('[name=commitA]').attr('value', sCommitA); |
|
363 |
+ welCommentTr.find('[name=commitB]').attr('value', sCommitB); |
|
364 |
+ welCommentTr.find('[name=commitId]').attr('value', sCommitId); |
|
406 | 365 |
|
407 | 366 |
welTr.after(htElement.welCommentTr); |
408 | 367 |
_updateMiniMap(); |
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?