(refs #9) Comments for commit and diff

This commit is contained in:
Shintaro Murakami
2014-10-25 10:22:31 +09:00
parent 823c52e941
commit af58e99dcf
31 changed files with 745 additions and 181 deletions

View File

@@ -0,0 +1,16 @@
CREATE TABLE COMMIT_COMMENT (
USER_NAME VARCHAR(100) NOT NULL,
REPOSITORY_NAME VARCHAR(100) NOT NULL,
COMMIT_ID VARCHAR(100) NOT NULL,
COMMENT_ID INT AUTO_INCREMENT,
COMMENTED_USER_NAME VARCHAR(100) NOT NULL,
CONTENT TEXT NOT NULL,
FILE_NAME NVARCHAR(100),
OLD_LINE_NUMBER INT,
NEW_LINE_NUMBER INT,
REGISTERED_DATE TIMESTAMP NOT NULL,
UPDATED_DATE TIMESTAMP NOT NULL
);
ALTER TABLE COMMIT_COMMENT ADD CONSTRAINT IDX_COMMIT_COMMENT_PK PRIMARY KEY (COMMENT_ID);
ALTER TABLE COMMIT_COMMENT ADD CONSTRAINT IDX_COMMIT_COMMENT_1 UNIQUE (USER_NAME, REPOSITORY_NAME, COMMIT_ID, COMMENT_ID);

View File

@@ -12,23 +12,21 @@ import scala.collection.JavaConverters._
import org.eclipse.jgit.lib.{ObjectId, CommitBuilder, PersonIdent} import org.eclipse.jgit.lib.{ObjectId, CommitBuilder, PersonIdent}
import service.IssuesService._ import service.IssuesService._
import service.PullRequestService._ import service.PullRequestService._
import util.JGitUtil.DiffInfo
import util.JGitUtil.CommitInfo
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.eclipse.jgit.merge.MergeStrategy import org.eclipse.jgit.merge.MergeStrategy
import org.eclipse.jgit.errors.NoMergeBaseException import org.eclipse.jgit.errors.NoMergeBaseException
import service.WebHookService.WebHookPayload import service.WebHookService.WebHookPayload
import util.JGitUtil.DiffInfo import util.JGitUtil.DiffInfo
import scala.Some
import util.JGitUtil.CommitInfo import util.JGitUtil.CommitInfo
class PullRequestsController extends PullRequestsControllerBase class PullRequestsController extends PullRequestsControllerBase
with RepositoryService with AccountService with IssuesService with PullRequestService with MilestonesService with LabelsService with RepositoryService with AccountService with IssuesService with PullRequestService with MilestonesService with LabelsService
with ActivityService with WebHookService with ReferrerAuthenticator with CollaboratorsAuthenticator with CommitsService with ActivityService with WebHookService with ReferrerAuthenticator with CollaboratorsAuthenticator
trait PullRequestsControllerBase extends ControllerBase { trait PullRequestsControllerBase extends ControllerBase {
self: RepositoryService with AccountService with IssuesService with MilestonesService with LabelsService self: RepositoryService with AccountService with IssuesService with MilestonesService with LabelsService
with ActivityService with PullRequestService with WebHookService with ReferrerAuthenticator with CollaboratorsAuthenticator => with CommitsService with ActivityService with PullRequestService with WebHookService with ReferrerAuthenticator with CollaboratorsAuthenticator =>
private val logger = LoggerFactory.getLogger(classOf[PullRequestsControllerBase]) private val logger = LoggerFactory.getLogger(classOf[PullRequestsControllerBase])
@@ -81,7 +79,8 @@ trait PullRequestsControllerBase extends ControllerBase {
pulls.html.pullreq( pulls.html.pullreq(
issue, pullreq, issue, pullreq,
getComments(owner, name, issueId), (commits.flatten.map(commit => getCommitComments(owner, name, commit.id)).flatten.toList ::: getComments(owner, name, issueId))
.sortWith((a, b) => a.registeredDate before b.registeredDate),
getIssueLabels(owner, name, issueId), getIssueLabels(owner, name, issueId),
(getCollaborators(owner, name) ::: (if(getAccountByUserName(owner).get.isGroupAccount) Nil else List(owner))).sorted, (getCollaborators(owner, name) ::: (if(getAccountByUserName(owner).get.isGroupAccount) Nil else List(owner))).sorted,
getMilestonesWithIssueCount(owner, name), getMilestonesWithIssueCount(owner, name),
@@ -281,6 +280,7 @@ trait PullRequestsControllerBase extends ControllerBase {
case (Some(userName), Some(repositoryName)) => (userName, repositoryName) :: getForkedRepositories(userName, repositoryName) case (Some(userName), Some(repositoryName)) => (userName, repositoryName) :: getForkedRepositories(userName, repositoryName)
case _ => (forkedRepository.owner, forkedRepository.name) :: getForkedRepositories(forkedRepository.owner, forkedRepository.name) case _ => (forkedRepository.owner, forkedRepository.name) :: getForkedRepositories(forkedRepository.owner, forkedRepository.name)
}, },
commits.flatten.map(commit => getCommitComments(forkedRepository.owner, forkedRepository.name, commit.id)).flatten.toList,
originBranch, originBranch,
forkedBranch, forkedBranch,
oldId.getName, oldId.getName,

View File

@@ -20,16 +20,16 @@ import org.eclipse.jgit.revwalk.RevCommit
import service.WebHookService.WebHookPayload import service.WebHookService.WebHookPayload
class RepositoryViewerController extends RepositoryViewerControllerBase class RepositoryViewerController extends RepositoryViewerControllerBase
with RepositoryService with AccountService with ActivityService with IssuesService with WebHookService with RepositoryService with AccountService with ActivityService with IssuesService with WebHookService with CommitsService
with ReferrerAuthenticator with CollaboratorsAuthenticator with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator
/** /**
* The repository viewer. * The repository viewer.
*/ */
trait RepositoryViewerControllerBase extends ControllerBase { trait RepositoryViewerControllerBase extends ControllerBase {
self: RepositoryService with AccountService with ActivityService with IssuesService with WebHookService self: RepositoryService with AccountService with ActivityService with IssuesService with WebHookService with CommitsService
with ReferrerAuthenticator with CollaboratorsAuthenticator => with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator =>
ArchiveCommand.registerFormat("zip", new ZipFormat) ArchiveCommand.registerFormat("zip", new ZipFormat)
ArchiveCommand.registerFormat("tar.gz", new TgzFormat) ArchiveCommand.registerFormat("tar.gz", new TgzFormat)
@@ -52,6 +52,13 @@ trait RepositoryViewerControllerBase extends ControllerBase {
fileName: String fileName: String
) )
case class CommentForm(
fileName: Option[String],
oldLineNumber: Option[Int],
newLineNumber: Option[Int],
content: String
)
val editorForm = mapping( val editorForm = mapping(
"branch" -> trim(label("Branch", text(required))), "branch" -> trim(label("Branch", text(required))),
"path" -> trim(label("Path", text())), "path" -> trim(label("Path", text())),
@@ -70,6 +77,13 @@ trait RepositoryViewerControllerBase extends ControllerBase {
"fileName" -> trim(label("Filename", text(required))) "fileName" -> trim(label("Filename", text(required)))
)(DeleteForm.apply) )(DeleteForm.apply)
val commentForm = mapping(
"fileName" -> trim(label("Filename", optional(text()))),
"oldLineNumber" -> trim(label("Old line number", optional(number()))),
"newLineNumber" -> trim(label("New line number", optional(number()))),
"content" -> trim(label("Content", text(required)))
)(CommentForm.apply)
/** /**
* Returns converted HTML from Markdown for preview. * Returns converted HTML from Markdown for preview.
*/ */
@@ -221,12 +235,81 @@ trait RepositoryViewerControllerBase extends ControllerBase {
repo.html.commit(id, new JGitUtil.CommitInfo(revCommit), repo.html.commit(id, new JGitUtil.CommitInfo(revCommit),
JGitUtil.getBranchesOfCommit(git, revCommit.getName), JGitUtil.getBranchesOfCommit(git, revCommit.getName),
JGitUtil.getTagsOfCommit(git, revCommit.getName), JGitUtil.getTagsOfCommit(git, revCommit.getName),
repository, diffs, oldCommitId) getCommitComments(repository.owner, repository.name, id),
repository, diffs, oldCommitId, hasWritePermission(repository.owner, repository.name, context.loginAccount))
} }
} }
} }
}) })
post("/:owner/:repository/commit/:id/comment/new", commentForm)(readableUsersOnly { (form, repository) =>
val id = params("id")
createCommitComment(repository.owner, repository.name, id, context.loginAccount.get.userName, form.content,
form.fileName, form.oldLineNumber, form.newLineNumber)
recordCommentCommitActivity(repository.owner, repository.name, context.loginAccount.get.userName, id, form.content)
redirect(s"/${repository.owner}/${repository.name}/commit/${id}")
})
ajaxGet("/:owner/:repository/commit/:id/comment/_form")(readableUsersOnly { repository =>
val id = params("id")
val fileName = params.get("fileName")
val oldLineNumber = params.get("oldLineNumber") flatMap {b => Some(b.toInt)}
val newLineNumber = params.get("newLineNumber") flatMap {b => Some(b.toInt)}
repo.html.commentform(
commitId = id,
fileName, oldLineNumber, newLineNumber,
hasWritePermission = hasWritePermission(repository.owner, repository.name, context.loginAccount),
repository = repository
)
})
ajaxPost("/:owner/:repository/commit/:id/comment/_data/new", commentForm)(readableUsersOnly { (form, repository) =>
val id = params("id")
val commentId = createCommitComment(repository.owner, repository.name, id, context.loginAccount.get.userName,
form.content, form.fileName, form.oldLineNumber, form.newLineNumber)
recordCommentCommitActivity(repository.owner, repository.name, context.loginAccount.get.userName, id, form.content)
helper.html.commitcomment(getCommitComment(repository.owner, repository.name, commentId.toString).get,
hasWritePermission(repository.owner, repository.name, context.loginAccount), repository)
})
ajaxGet("/:owner/:repository/commit_comments/_data/:id")(readableUsersOnly { repository =>
getCommitComment(repository.owner, repository.name, params("id")) map { x =>
if(isEditable(x.userName, x.repositoryName, x.commentedUserName)){
params.get("dataType") collect {
case t if t == "html" => repo.html.editcomment(
x.content, x.commentId, x.userName, x.repositoryName)
} getOrElse {
contentType = formats("json")
org.json4s.jackson.Serialization.write(
Map("content" -> view.Markdown.toHtml(x.content,
repository, false, true, true, isEditable(x.userName, x.repositoryName, x.commentedUserName))
))
}
} else Unauthorized
} getOrElse NotFound
})
ajaxPost("/:owner/:repository/commit_comments/edit/:id", commentForm)(readableUsersOnly { (form, repository) =>
defining(repository.owner, repository.name){ case (owner, name) =>
getCommitComment(owner, name, params("id")).map { comment =>
if(isEditable(owner, name, comment.commentedUserName)){
updateCommitComment(comment.commentId, form.content)
redirect(s"/${owner}/${name}/commit_comments/_data/${comment.commentId}")
} else Unauthorized
} getOrElse NotFound
}
})
ajaxPost("/:owner/:repository/commit_comments/delete/:id")(readableUsersOnly { repository =>
defining(repository.owner, repository.name){ case (owner, name) =>
getCommitComment(owner, name, params("id")).map { comment =>
if(isEditable(owner, name, comment.commentedUserName)){
Ok(deleteCommitComment(comment.commentId))
} else Unauthorized
} getOrElse NotFound
}
})
/** /**
* Displays branches. * Displays branches.
*/ */
@@ -461,4 +544,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
file file
} }
} }
private def isEditable(owner: String, repository: String, author: String)(implicit context: app.Context): Boolean =
hasWritePermission(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName
} }

View File

@@ -44,4 +44,11 @@ protected[model] trait TemplateComponent { self: Profile =>
byRepository(userName, repositoryName) && (this.milestoneId === milestoneId) byRepository(userName, repositoryName) && (this.milestoneId === milestoneId)
} }
trait CommitTemplate extends BasicTemplate { self: Table[_] =>
val commitId = column[String]("COMMIT_ID")
def byCommit(owner: String, repository: String, commitId: String) =
byRepository(owner, repository) && (this.commitId === commitId)
}
} }

View File

@@ -0,0 +1,76 @@
package model
trait Comment {
val commentedUserName: String
val registeredDate: java.util.Date
}
trait IssueCommentComponent extends TemplateComponent { self: Profile =>
import profile.simple._
import self._
lazy val IssueComments = new TableQuery(tag => new IssueComments(tag)){
def autoInc = this returning this.map(_.commentId)
}
class IssueComments(tag: Tag) extends Table[IssueComment](tag, "ISSUE_COMMENT") with IssueTemplate {
val commentId = column[Int]("COMMENT_ID", O AutoInc)
val action = column[String]("ACTION")
val commentedUserName = column[String]("COMMENTED_USER_NAME")
val content = column[String]("CONTENT")
val registeredDate = column[java.util.Date]("REGISTERED_DATE")
val updatedDate = column[java.util.Date]("UPDATED_DATE")
def * = (userName, repositoryName, issueId, commentId, action, commentedUserName, content, registeredDate, updatedDate) <> (IssueComment.tupled, IssueComment.unapply)
def byPrimaryKey(commentId: Int) = this.commentId === commentId.bind
}
}
case class IssueComment (
userName: String,
repositoryName: String,
issueId: Int,
commentId: Int = 0,
action: String,
commentedUserName: String,
content: String,
registeredDate: java.util.Date,
updatedDate: java.util.Date
) extends Comment
trait CommitCommentComponent extends TemplateComponent { self: Profile =>
import profile.simple._
import self._
lazy val CommitComments = new TableQuery(tag => new CommitComments(tag)){
def autoInc = this returning this.map(_.commentId)
}
class CommitComments(tag: Tag) extends Table[CommitComment](tag, "COMMIT_COMMENT") with CommitTemplate {
val commentId = column[Int]("COMMENT_ID", O AutoInc)
val commentedUserName = column[String]("COMMENTED_USER_NAME")
val content = column[String]("CONTENT")
val fileName = column[Option[String]]("FILE_NAME")
val oldLine = column[Option[Int]]("OLD_LINE_NUMBER")
val newLine = column[Option[Int]]("NEW_LINE_NUMBER")
val registeredDate = column[java.util.Date]("REGISTERED_DATE")
val updatedDate = column[java.util.Date]("UPDATED_DATE")
def * = (userName, repositoryName, commitId, commentId, commentedUserName, content, fileName, oldLine, newLine, registeredDate, updatedDate) <> (CommitComment.tupled, CommitComment.unapply)
def byPrimaryKey(commentId: Int) = this.commentId === commentId.bind
}
}
case class CommitComment(
userName: String,
repositoryName: String,
commitId: String,
commentId: Int = 0,
commentedUserName: String,
content: String,
fileName: Option[String],
oldLine: Option[Int],
newLine: Option[Int],
registeredDate: java.util.Date,
updatedDate: java.util.Date
) extends Comment

View File

@@ -1,34 +0,0 @@
package model
trait IssueCommentComponent extends TemplateComponent { self: Profile =>
import profile.simple._
import self._
lazy val IssueComments = new TableQuery(tag => new IssueComments(tag)){
def autoInc = this returning this.map(_.commentId)
}
class IssueComments(tag: Tag) extends Table[IssueComment](tag, "ISSUE_COMMENT") with IssueTemplate {
val commentId = column[Int]("COMMENT_ID", O AutoInc)
val action = column[String]("ACTION")
val commentedUserName = column[String]("COMMENTED_USER_NAME")
val content = column[String]("CONTENT")
val registeredDate = column[java.util.Date]("REGISTERED_DATE")
val updatedDate = column[java.util.Date]("UPDATED_DATE")
def * = (userName, repositoryName, issueId, commentId, action, commentedUserName, content, registeredDate, updatedDate) <> (IssueComment.tupled, IssueComment.unapply)
def byPrimaryKey(commentId: Int) = this.commentId === commentId.bind
}
}
case class IssueComment(
userName: String,
repositoryName: String,
issueId: Int,
commentId: Int = 0,
action: String,
commentedUserName: String,
content: String,
registeredDate: java.util.Date,
updatedDate: java.util.Date
)

View File

@@ -22,6 +22,7 @@ object Profile extends {
} with AccountComponent } with AccountComponent
with ActivityComponent with ActivityComponent
with CollaboratorComponent with CollaboratorComponent
with CommitCommentComponent
with GroupMemberComponent with GroupMemberComponent
with IssueComponent with IssueComponent
with IssueCommentComponent with IssueCommentComponent

View File

@@ -95,6 +95,15 @@ trait ActivityService {
Some(cut(comment, 200)), Some(cut(comment, 200)),
currentDate) currentDate)
def recordCommentCommitActivity(userName: String, repositoryName: String, activityUserName: String, commitId: String, comment: String)
(implicit s: Session): Unit =
Activities insert Activity(userName, repositoryName, activityUserName,
"comment_commit",
s"[user:${activityUserName}] commented on commit [commit:${userName}/${repositoryName}@${commitId}]",
Some(cut(comment, 200)),
currentDate
)
def recordCreateWikiPageActivity(userName: String, repositoryName: String, activityUserName: String, pageName: String) def recordCreateWikiPageActivity(userName: String, repositoryName: String, activityUserName: String, pageName: String)
(implicit s: Session): Unit = (implicit s: Session): Unit =
Activities insert Activity(userName, repositoryName, activityUserName, Activities insert Activity(userName, repositoryName, activityUserName,

View File

@@ -0,0 +1,49 @@
package service
import scala.slick.jdbc.{StaticQuery => Q}
import Q.interpolation
import model.Profile._
import profile.simple._
import model.CommitComment
import util.Implicits._
import util.StringUtil._
trait CommitsService {
def getCommitComments(owner: String, repository: String, commitId: String)(implicit s: Session) =
CommitComments filter (_.byCommit(owner, repository, commitId)) list
def getCommitComment(owner: String, repository: String, commentId: String)(implicit s: Session) =
if (commentId forall (_.isDigit))
CommitComments filter { t =>
t.byPrimaryKey(commentId.toInt) && t.byRepository(owner, repository)
} firstOption
else
None
def createCommitComment(owner: String, repository: String, commitId: String, loginUser: String,
content: String, fileName: Option[String], oldLine: Option[Int], newLine: Option[Int])(implicit s: Session): Int =
CommitComments.autoInc insert CommitComment(
userName = owner,
repositoryName = repository,
commitId = commitId,
commentedUserName = loginUser,
content = content,
fileName = fileName,
oldLine = oldLine,
newLine = newLine,
registeredDate = currentDate,
updatedDate = currentDate)
def updateCommitComment(commentId: Int, content: String)(implicit s: Session) =
CommitComments
.filter (_.byPrimaryKey(commentId))
.map { t =>
t.content -> t.updatedDate
}.update (content, currentDate)
def deleteCommitComment(commentId: Int)(implicit s: Session) =
CommitComments filter (_.byPrimaryKey(commentId)) delete
}

View File

@@ -53,6 +53,7 @@ object AutoUpdate {
* The history of versions. A head of this sequence is the current BitBucket version. * The history of versions. A head of this sequence is the current BitBucket version.
*/ */
val versions = Seq( val versions = Seq(
new Version(2, 7),
new Version(2, 6), new Version(2, 6),
new Version(2, 5), new Version(2, 5),
new Version(2, 4), new Version(2, 4),

View File

@@ -152,6 +152,7 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
.replaceAll("\\[branch:([^\\s]+?)/([^\\s]+?)#([^\\s]+?)\\]", (m: Match) => s"""<a href="${context.path}/${m.group(1)}/${m.group(2)}/tree/${encodeRefName(m.group(3))}">${m.group(3)}</a>""") .replaceAll("\\[branch:([^\\s]+?)/([^\\s]+?)#([^\\s]+?)\\]", (m: Match) => s"""<a href="${context.path}/${m.group(1)}/${m.group(2)}/tree/${encodeRefName(m.group(3))}">${m.group(3)}</a>""")
.replaceAll("\\[tag:([^\\s]+?)/([^\\s]+?)#([^\\s]+?)\\]" , (m: Match) => s"""<a href="${context.path}/${m.group(1)}/${m.group(2)}/tree/${encodeRefName(m.group(3))}">${m.group(3)}</a>""") .replaceAll("\\[tag:([^\\s]+?)/([^\\s]+?)#([^\\s]+?)\\]" , (m: Match) => s"""<a href="${context.path}/${m.group(1)}/${m.group(2)}/tree/${encodeRefName(m.group(3))}">${m.group(3)}</a>""")
.replaceAll("\\[user:([^\\s]+?)\\]" , (m: Match) => user(m.group(1)).body) .replaceAll("\\[user:([^\\s]+?)\\]" , (m: Match) => user(m.group(1)).body)
.replaceAll("\\[commit:([^\\s]+?)/([^\\s]+?)\\@([^\\s]+?)\\]", (m: Match) => s"""<a href="${context.path}/${m.group(1)}/${m.group(2)}/commit/${m.group(3)}">${m.group(1)}/${m.group(2)}@${m.group(3).substring(0, 7)}</a>""")
) )
/** /**

View File

@@ -10,6 +10,7 @@
@(activity.activityType match { @(activity.activityType match {
case "open_issue" => detailActivity(activity, "activity-issue.png") case "open_issue" => detailActivity(activity, "activity-issue.png")
case "comment_issue" => detailActivity(activity, "activity-comment.png") case "comment_issue" => detailActivity(activity, "activity-comment.png")
case "comment_commit" => detailActivity(activity, "activity-comment.png")
case "close_issue" => detailActivity(activity, "activity-issue-close.png") case "close_issue" => detailActivity(activity, "activity-issue-close.png")
case "reopen_issue" => detailActivity(activity, "activity-issue-reopen.png") case "reopen_issue" => detailActivity(activity, "activity-issue-reopen.png")
case "open_pullreq" => detailActivity(activity, "activity-merge.png") case "open_pullreq" => detailActivity(activity, "activity-merge.png")

View File

@@ -7,6 +7,7 @@
@defining("(id=\")([\\w\\-]*)(\")".r.findFirstMatchIn(textarea.body).map(_.group(2))){ textareaId => @defining("(id=\")([\\w\\-]*)(\")".r.findFirstMatchIn(textarea.body).map(_.group(2))){ textareaId =>
<script> <script>
$(function(){ $(function(){
try {
$([$('#@textareaId').closest('div')[0], $('#@textareaId').next('div')[0]]).dropzone({ $([$('#@textareaId').closest('div')[0], $('#@textareaId').next('div')[0]]).dropzone({
url: '@path/upload/image/@owner/@repository', url: '@path/upload/image/@owner/@repository',
maxFilesize: 10, maxFilesize: 10,
@@ -19,6 +20,11 @@ $(function(){
$(file.previewElement).prevAll('div.dz-preview').addBack().remove(); $(file.previewElement).prevAll('div.dz-preview').addBack().remove();
} }
}); });
} catch(e) {
if (e.message !== "Dropzone already attached.") {
throw e;
}
}
// Adjust clickable area width // Adjust clickable area width
$('#@textareaId').next('div.clickable').css('width', ($('#@textareaId').width() + 8) + 'px'); $('#@textareaId').next('div.clickable').css('width', ($('#@textareaId').width() + 8) + 'px');

View File

@@ -0,0 +1,30 @@
@(comment: model.CommitComment,
hasWritePermission: Boolean,
repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context)
@import context._
@import view.helpers._
<div class="@if(comment.fileName.isDefined){inline-comment}" @if(comment.fileName.isDefined){filename=@comment.fileName.get} @if(comment.newLine.isDefined){newline=@comment.newLine.get} @if(comment.oldLine.isDefined){oldline=@comment.oldLine.get}>
<div class="issue-avatar-image">@avatar(comment.commentedUserName, 48)</div>
<div class="box commit-comment-box commit-comment-@comment.commentId">
<div class="box-header-small">
@user(comment.commentedUserName, styleClass="username strong")
<span class="muted">
commented
@if(comment.fileName.isDefined){
on @comment.fileName.get
}
in <a href="@path/@repository.owner/@repository.name/commit/@comment.commitId">@comment.commitId.substring(0, 7)</a>
@helper.html.datetimeago(comment.registeredDate)
</span>
<span class="pull-right">
@if(hasWritePermission || loginAccount.map(_.userName == comment.commentedUserName).getOrElse(false)){
<a href="#" data-comment-id="@comment.commentId"><i class="icon-pencil"></i></a>&nbsp;
<a href="#" data-comment-id="@comment.commentId"><i class="icon-remove-circle"></i></a>
}
</span>
</div>
<div class="box-content commit-commentContent-@comment.commentId">
@markdown(comment.content, repository, false, true, true, hasWritePermission)
</div>
</div>
</div>

View File

@@ -2,7 +2,9 @@
repository: service.RepositoryService.RepositoryInfo, repository: service.RepositoryService.RepositoryInfo,
newCommitId: Option[String], newCommitId: Option[String],
oldCommitId: Option[String], oldCommitId: Option[String],
showIndex: Boolean)(implicit context: app.Context) showIndex: Boolean,
hasWritePermission: Boolean,
showLineNotes: Boolean)(implicit context: app.Context)
@import context._ @import context._
@import view.helpers._ @import view.helpers._
@import org.eclipse.jgit.diff.DiffEntry.ChangeType @import org.eclipse.jgit.diff.DiffEntry.ChangeType
@@ -39,7 +41,7 @@
} }
@diffs.zipWithIndex.map { case (diff, i) => @diffs.zipWithIndex.map { case (diff, i) =>
<a name="diff-@i"></a> <a name="diff-@i"></a>
<table class="table table-bordered"> <table class="table table-bordered" commitId="@newCommitId" fileName="@diff.newPath">
<tr> <tr>
<th style="font-weight: normal; line-height: 27px;" class="box-header"> <th style="font-weight: normal; line-height: 27px;" class="box-header">
@if(diff.changeType == ChangeType.COPY || diff.changeType == ChangeType.RENAME){ @if(diff.changeType == ChangeType.COPY || diff.changeType == ChangeType.RENAME){
@@ -118,6 +120,67 @@ $(function(){
} }
} }
} }
@if(showLineNotes){
$('.inline-comment').each(function(i, v) {
var $v = $(v), filename = $v.attr('filename'),
oldline = $v.attr('oldline'), newline = $v.attr('newline'),
tmp = $('<tr class="not-diff"><td colspan="3" style="white-space: initial; line-height: initial; padding: 10px;"></td></tr>');
tmp.children('td').html($(this).clone().show());
if (typeof $('#show-notes')[0] !== 'undefined' && !$('#show-notes')[0].checked) {
$(this).hide();
}
if (typeof oldline !== 'undefined') {
$('table[filename="' + filename + '"]').find('table.inlinediff').find('.oldline').filter(function() {
return new RegExp('^' + oldline + '$').test($(this).text());
}).parent().nextAll(':not(.not-diff):first').before(tmp);
} else {
$('table[filename="' + filename + '"]').find('table.inlinediff').find('.newline').filter(function() {
return new RegExp('^' + newline + '\\+$').test($(this).text());
}).parent().nextAll(':not(.not-diff):first').before(tmp);
}
});
@if(hasWritePermission) {
$('table.diff tr').hover(
function() {
$(this).find('b').css('display', 'inline-block');
},
function() {
$(this).find('b').css('display', 'none');
}
);
$('.add-comment').click(function() {
var $this = $(this),
$tr = $(this).closest('tr');
if (!$tr.nextAll(':not(.not-diff):first').prev().hasClass('inline-comment-form')) {
var commitId = $(this).closest('.table-bordered').attr('commitId'),
fileName = $(this).closest('.table-bordered').attr('fileName'),
oldLineNumber = $(this).closest('.newline').prev('.oldline').text(),
newLineNumber = $(this).closest('.newline').clone().children().remove().end().text(),
url = '@url(repository)/commit/' + commitId + '/comment/_form?fileName=' + fileName;
if (!isNaN(oldLineNumber) && oldLineNumber != null && oldLineNumber !== '') {
url += ('&oldLineNumber=' + oldLineNumber)
}
if (!isNaN(newLineNumber) && newLineNumber != null && newLineNumber !== '') {
url += ('&newLineNumber=' + newLineNumber)
}
$.get(
url,
{
dataType : 'html'
},
function(responseContent) {
$this.hide();
var tmp = $('<tr class="inline-comment-form not-diff"><td colspan="3" style="white-space: initial; padding: 10px;"></td></tr>');
tmp.children('td').html(responseContent);
$tr.nextAll(':not(.not-diff):first').before(tmp);
});
}
});
$('table.diff').on('click', '.btn-default', function() {
$(this).closest('.inline-comment-form').remove();
});
}
}
} }
}); });
</script> </script>

View File

@@ -34,7 +34,7 @@ $(function(){
} }
$('#preview').click(function(){ $('#preview').click(function(){
$('#preview-area').html('<img src="@assets/common/images/indicator.gif"> Previewing...'); $(this).closest('#preview-area').html('<img src="@assets/common/images/indicator.gif"> Previewing...');
$.post('@url(repository)/_preview', { $.post('@url(repository)/_preview', {
content : $('#content').val(), content : $('#content').val(),
enableWikiLink : @enableWikiLink, enableWikiLink : @enableWikiLink,

View File

@@ -1,26 +1,29 @@
@(issue: model.Issue, @(issue: Option[model.Issue],
comments: List[model.IssueComment], comments: List[model.Comment],
hasWritePermission: Boolean, hasWritePermission: Boolean,
repository: service.RepositoryService.RepositoryInfo, repository: service.RepositoryService.RepositoryInfo,
pullreq: Option[model.PullRequest] = None)(implicit context: app.Context) pullreq: Option[model.PullRequest] = None)(implicit context: app.Context)
@import context._ @import context._
@import view.helpers._ @import view.helpers._
<div class="issue-avatar-image">@avatar(issue.openedUserName, 48)</div> @if(issue.isDefined){
<div class="box issue-comment-box"> <div class="issue-avatar-image">@avatar(issue.get.openedUserName, 48)</div>
<div class="box issue-comment-box">
<div class="box-header-small"> <div class="box-header-small">
@user(issue.openedUserName, styleClass="username strong") <span class="muted">commented @helper.html.datetimeago(issue.registeredDate)</span> @user(issue.get.openedUserName, styleClass="username strong") <span class="muted">commented @helper.html.datetimeago(issue.get.registeredDate)</span>
<span class="pull-right"> <span class="pull-right">
@if(hasWritePermission || loginAccount.map(_.userName == issue.openedUserName).getOrElse(false)){ @if(hasWritePermission || loginAccount.map(_.userName == issue.get.openedUserName).getOrElse(false)){
<a href="#" data-issue-id="@issue.issueId"><i class="icon-pencil"></i></a> <a href="#" data-issue-id="@issue.get.issueId"><i class="icon-pencil"></i></a>
} }
</span> </span>
</div> </div>
<div class="box-content issue-content" id="issueContent"> <div class="box-content issue-content" id="issueContent">
@markdown(issue.content getOrElse "No description provided.", repository, false, true, true, hasWritePermission) @markdown(issue.get.content getOrElse "No description provided.", repository, false, true, true, hasWritePermission)
</div> </div>
</div> </div>
}
@comments.map { comment => @comments.map {
case comment: model.IssueComment => {
@if(comment.action != "close" && comment.action != "reopen" && comment.action != "delete_branch"){ @if(comment.action != "close" && comment.action != "reopen" && comment.action != "delete_branch"){
<div class="issue-avatar-image">@avatar(comment.commentedUserName, 48)</div> <div class="issue-avatar-image">@avatar(comment.commentedUserName, 48)</div>
<div class="box issue-comment-box" id="comment-@comment.commentId"> <div class="box issue-comment-box" id="comment-@comment.commentId">
@@ -35,8 +38,8 @@
@helper.html.datetimeago(comment.registeredDate) @helper.html.datetimeago(comment.registeredDate)
</span> </span>
<span class="pull-right"> <span class="pull-right">
@if(comment.action != "commit" && comment.action != "merge" && comment.action != "refer" && @if(comment.action != "commit" && comment.action != "merge" && comment.action != "refer"
(hasWritePermission || loginAccount.map(_.userName == comment.commentedUserName).getOrElse(false))){ && (hasWritePermission || loginAccount.map(_.userName == comment.commentedUserName).getOrElse(false))){
<a href="#" data-comment-id="@comment.commentId"><i class="icon-pencil"></i></a>&nbsp; <a href="#" data-comment-id="@comment.commentId"><i class="icon-pencil"></i></a>&nbsp;
<a href="#" data-comment-id="@comment.commentId"><i class="icon-remove-circle"></i></a> <a href="#" data-comment-id="@comment.commentId"><i class="icon-remove-circle"></i></a>
} }
@@ -77,7 +80,7 @@
<div class="small issue-comment-action"> <div class="small issue-comment-action">
<span class="label label-important">Closed</span> <span class="label label-important">Closed</span>
@avatar(comment.commentedUserName, 20) @avatar(comment.commentedUserName, 20)
@if(issue.isPullRequest){ @if(issue.isDefined && issue.get.isPullRequest){
@user(comment.commentedUserName, styleClass="username strong") closed the pull request @helper.html.datetimeago(comment.registeredDate) @user(comment.commentedUserName, styleClass="username strong") closed the pull request @helper.html.datetimeago(comment.registeredDate)
} else { } else {
@user(comment.commentedUserName, styleClass="username strong") closed the issue @helper.html.datetimeago(comment.registeredDate) @user(comment.commentedUserName, styleClass="username strong") closed the issue @helper.html.datetimeago(comment.registeredDate)
@@ -98,10 +101,15 @@
@user(comment.commentedUserName, styleClass="username strong") deleted the <span class="label label-info monospace">@pullreq.map(_.requestBranch)</span> branch @helper.html.datetimeago(comment.registeredDate) @user(comment.commentedUserName, styleClass="username strong") deleted the <span class="label label-info monospace">@pullreq.map(_.requestBranch)</span> branch @helper.html.datetimeago(comment.registeredDate)
</div> </div>
} }
}
case comment: model.CommitComment => {
@helper.html.commitcomment(comment, hasWritePermission, repository)
}
} }
<script> <script>
$(function(){ $(function(){
$('i.icon-pencil').click(function(){ @if(issue.isDefined){
$('.issue-comment-box i.icon-pencil').click(function(){
var id = $(this).closest('a').data('comment-id'); var id = $(this).closest('a').data('comment-id');
var url = '@url(repository)/issue_comments/_data/' + id; var url = '@url(repository)/issue_comments/_data/' + id;
var $content = $('#commentContent-' + id); var $content = $('#commentContent-' + id);
@@ -134,6 +142,34 @@ $(function(){
} }
return false; return false;
}); });
}
$(document).on('click', '.commit-comment-box i.icon-pencil', function(){
var id = $(this).closest('a').data('comment-id');
var url = '@url(repository)/commit_comments/_data/' + id;
var $content = $('.commit-commentContent-' + id, $(this).closest('.box'));
$.get(url,
{
dataType : 'html'
},
function(data){
$content.empty().html(data);
});
return false;
});
$(document).on('click', '.commit-comment-box i.icon-remove-circle', function(){
if(confirm('Are you sure you want to delete this?')) {
var id = $(this).closest('a').data('comment-id');
$.post('@url(repository)/commit_comments/delete/' + id,
function(data){
if(data > 0) {
$('.commit-comment-' + id).closest('.not-diff').remove();
$('.commit-comment-' + id).closest('.inline-comment').remove();
}
});
}
return false;
});
var extractMarkdown = function(data){ var extractMarkdown = function(data){
$('body').append('<div id="tmp"></div>'); $('body').append('<div id="tmp"></div>');
@@ -156,15 +192,40 @@ $(function(){
return ss.join(''); return ss.join('');
}; };
$('#issueContent').on('click', ':checkbox', function(ev){ $('div[class*=commit-commentContent-]').on('click', ':checkbox', function(ev){
var checkboxes = $('#issueContent :checkbox'); var $commentContent = $(ev.target).parents('div[class*=commit-commentContent-]'),
$.get('@url(repository)/issues/_data/@issue.issueId', commentId = $commentContent.attr('class').match(/commit-commentContent-.+/)[0].replace(/commit-commentContent-/, ''),
checkboxes = $commentContent.find(':checkbox');
$.get('@url(repository)/commit_comments/_data/' + commentId,
{ {
dataType : 'html' dataType : 'html'
}, },
function(responseContent){ function(responseContent){
$.ajax({ $.ajax({
url: '@url(repository)/issues/edit/@issue.issueId', url: '@url(repository)/commit_comments/edit/' + commentId,
type: 'POST',
data: {
issueId : 0,
content : replaceTaskList(responseContent, checkboxes)
},
success: function(data) {
$('.commit-commentContent-' + commentId).html(data.content);
}
});
}
);
});
@if(issue.isDefined){
$('#issueContent').on('click', ':checkbox', function(ev){
var checkboxes = $('#issueContent :checkbox');
$.get('@url(repository)/issues/_data/@issue.get.issueId',
{
dataType : 'html'
},
function(responseContent){
$.ajax({
url: '@url(repository)/issues/edit/@issue.get.issueId',
type: 'POST', type: 'POST',
data: { data: {
title : $('#issueTitle').text(), title : $('#issueTitle').text(),
@@ -196,5 +257,7 @@ $(function(){
); );
}); });
}
}); });
</script> </script>

View File

@@ -47,7 +47,7 @@
<hr> <hr>
<div class="row-fluid"> <div class="row-fluid">
<div class="span10"> <div class="span10">
@commentlist(issue, comments, hasWritePermission, repository) @commentlist(Some(issue), comments, hasWritePermission, repository)
@commentform(issue, true, hasWritePermission, repository) @commentform(issue, true, hasWritePermission, repository)
</div> </div>
<div class="span2"> <div class="span2">

View File

@@ -1,5 +1,5 @@
@(issue: model.Issue, @(issue: model.Issue,
comments: List[model.IssueComment], comments: List[model.Comment],
issueLabels: List[model.Label], issueLabels: List[model.Label],
collaborators: List[String], collaborators: List[String],
milestones: List[(model.Milestone, Int, Int)], milestones: List[(model.Milestone, Int, Int)],

View File

@@ -1,4 +1,5 @@
@(commits: Seq[Seq[util.JGitUtil.CommitInfo]], @(commits: Seq[Seq[util.JGitUtil.CommitInfo]],
comments: Option[List[model.Comment]] = None,
repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context) repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context)
@import context._ @import context._
@import view.helpers._ @import view.helpers._
@@ -15,6 +16,14 @@
@user(commit.authorName, commit.authorEmailAddress, "username") @user(commit.authorName, commit.authorEmailAddress, "username")
</td> </td>
<td>@commit.shortMessage</td> <td>@commit.shortMessage</td>
<td style="width: 10%; text-align: right">
<span class="badge" style="display: inline">@if(comments.isDefined){
@comments.get.flatMap @{
case comment: model.CommitComment => Some(comment)
case other => None
}.filter(_.commitId == commit.id).size
}</span>
</td>
<td style="width: 10%; text-align: right;"> <td style="width: 10%; text-align: right;">
<a href="@url(repository)/commit/@commit.id" class="monospace">@commit.id.substring(0, 7)</a> <a href="@url(repository)/commit/@commit.id" class="monospace">@commit.id.substring(0, 7)</a>
</td> </td>

View File

@@ -1,6 +1,7 @@
@(commits: Seq[Seq[util.JGitUtil.CommitInfo]], @(commits: Seq[Seq[util.JGitUtil.CommitInfo]],
diffs: Seq[util.JGitUtil.DiffInfo], diffs: Seq[util.JGitUtil.DiffInfo],
members: List[(String, String)], members: List[(String, String)],
comments: List[model.Comment],
originId: String, originId: String,
forkedId: String, forkedId: String,
sourceId: String, sourceId: String,
@@ -81,8 +82,10 @@
</tr> </tr>
</table> </table>
} else { } else {
@pulls.html.commits(commits, repository) @pulls.html.commits(commits, Some(comments), repository)
@helper.html.diff(diffs, repository, Some(commitId), Some(sourceId), true) @helper.html.diff(diffs, repository, Some(commitId), Some(sourceId), true, hasWritePermission, false)
<p>Showing you all comments on commits in this comparison.</p>
@issues.html.commentlist(None, comments, hasWritePermission, repository, None)
} }
} }
} }

View File

@@ -1,6 +1,6 @@
@(issue: model.Issue, @(issue: model.Issue,
pullreq: model.PullRequest, pullreq: model.PullRequest,
comments: List[model.IssueComment], comments: List[model.Comment],
issueLabels: List[model.Label], issueLabels: List[model.Label],
collaborators: List[String], collaborators: List[String],
milestones: List[(model.Milestone, Int, Int)], milestones: List[(model.Milestone, Int, Int)],
@@ -8,11 +8,17 @@
hasWritePermission: Boolean, hasWritePermission: Boolean,
repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context) repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context)
@import context._ @import context._
@import model.IssueComment
@import view.helpers._ @import view.helpers._
<div class="row-fluid"> <div class="row-fluid">
<div class="span10"> <div class="span10">
@issues.html.commentlist(issue, comments, hasWritePermission, repository, Some(pullreq)) <div id="comment-list">
@defining(comments.exists(_.action == "merge")){ merged => @issues.html.commentlist(Some(issue), comments, hasWritePermission, repository, Some(pullreq))
</div>
@defining(comments.flatMap {
case comment: IssueComment => Some(comment)
case other => None
}.exists(_.action == "merge")){ merged =>
@if(hasWritePermission && !issue.closed){ @if(hasWritePermission && !issue.closed){
<div class="box issue-comment-box" style="background-color: #d8f5cd;"> <div class="box issue-comment-box" style="background-color: #d8f5cd;">
<div class="box-content"class="issue-content" style="border: 1px solid #95c97e; padding: 10px;"> <div class="box-content"class="issue-content" style="border: 1px solid #95c97e; padding: 10px;">

View File

@@ -1,6 +1,6 @@
@(issue: model.Issue, @(issue: model.Issue,
pullreq: model.PullRequest, pullreq: model.PullRequest,
comments: List[model.IssueComment], comments: List[model.Comment],
issueLabels: List[model.Label], issueLabels: List[model.Label],
collaborators: List[String], collaborators: List[String],
milestones: List[(model.Milestone, Int, Int)], milestones: List[(model.Milestone, Int, Int)],
@@ -36,7 +36,10 @@
</h1> </h1>
</div> </div>
@if(issue.closed) { @if(issue.closed) {
@comments.find(_.action == "merge").map{ comment => @comments.flatMap @{
case comment: model.IssueComment => Some(comment)
case _ => None
}.find(_.action == "merge").map{ comment =>
<span class="label label-info issue-status">Merged</span> <span class="label label-info issue-status">Merged</span>
<span class="muted"> <span class="muted">
@user(comment.commentedUserName, styleClass="username strong") merged @commits.size @plural(commits.size, "commit") @user(comment.commentedUserName, styleClass="username strong") merged @commits.size @plural(commits.size, "commit")
@@ -59,7 +62,10 @@
} }
<br/><br/> <br/><br/>
<ul class="nav nav-tabs fill-width pull-left" id="pullreq-tab"> <ul class="nav nav-tabs fill-width pull-left" id="pullreq-tab">
<li class="active"><a href="#conversation">Conversation <span class="badge">@comments.size</span></a></li> <li class="active"><a href="#conversation">Conversation <span class="badge">@comments.flatMap @{
case comment: model.IssueComment => Some(comment)
case _: model.CommitComment => None
}.size</span></a></li>
<li><a href="#commits">Commits <span class="badge">@commits.size</span></a></li> <li><a href="#commits">Commits <span class="badge">@commits.size</span></a></li>
<li><a href="#files">Files Changed <span class="badge">@diffs.size</span></a></li> <li><a href="#files">Files Changed <span class="badge">@diffs.size</span></a></li>
</ul> </ul>
@@ -68,10 +74,10 @@
@pulls.html.conversation(issue, pullreq, comments, issueLabels, collaborators, milestones, labels, hasWritePermission, repository) @pulls.html.conversation(issue, pullreq, comments, issueLabels, collaborators, milestones, labels, hasWritePermission, repository)
</div> </div>
<div class="tab-pane" id="commits"> <div class="tab-pane" id="commits">
@pulls.html.commits(dayByDayCommits, repository) @pulls.html.commits(dayByDayCommits, Some(comments), repository)
</div> </div>
<div class="tab-pane" id="files"> <div class="tab-pane" id="files">
@helper.html.diff(diffs, repository, Some(commits.head.id), Some(commits.last.id), true) @helper.html.diff(diffs, repository, Some(commits.head.id), Some(commits.last.id), true, hasWritePermission, true)
</div> </div>
</div> </div>
} }

View File

@@ -0,0 +1,59 @@
@(commitId: String,
fileName: Option[String] = None,
oldLineNumber: Option[Int] = None,
newLineNumber: Option[Int] = None,
hasWritePermission: Boolean,
repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context)
@import context._
@import view.helpers._
@if(loginAccount.isDefined){
@if(!fileName.isDefined){<hr/><br/>}
<form method="POST" validate="true" style="max-width: 874px;">
@if(!fileName.isDefined){
<div class="issue-avatar-image">@avatar(loginAccount.get.userName, 48)</div>
}
<div class="box issue-comment-box">
<div class="box-content">
@helper.html.preview(repository, "", false, true, true, hasWritePermission, "width: 635px; height: 100px; max-height: 150px;", elastic = true)
</div>
@if(fileName.isDefined){
<div class="pull-right" style="margin-top: 10px;">
<input type="button" class="btn btn-default" value="Cancel"/>
<input type="submit" class="btn btn-success btn-inline-comment" formaction="@url(repository)/commit/@commitId/comment/new" value="Comment"/>
</div>
}
</div>
@if(!fileName.isDefined){
<div class="pull-right">
<input type="submit" class="btn btn-success" formaction="@url(repository)/commit/@commitId/comment/new" value="Comment on this commit"/>
</div>
}
@if(fileName.isDefined){<input type="hidden" name="fileName" value="@fileName.get">}
@if(oldLineNumber.isDefined){<input type="hidden" name="oldLineNumber" value="@oldLineNumber.get">}
@if(newLineNumber.isDefined){<input type="hidden" name="newLineNumber" value="@newLineNumber.get">}
</form>
<script>
$('.btn-inline-comment').click(function(e) {
e.preventDefault();
$form = $(e.target).attr('disabled', 'disabled').closest('form');
var param = {};
$($form.serializeArray()).each(function(i, v) {
param[v.name] = v.value;
});
$.ajax({
url: '@url(repository)/commit/@commitId/comment/_data/new',
type: 'POST',
data: param
}).done(function(data) {
$form.closest('tr').removeClass('inline-comment-form').find('td').html('<td colspan="3"></td>').html(data);
$('#comment-list').append(data);
if (typeof $('#show-notes')[0] !== 'undefined' && !$('#show-notes')[0].checked) {
$('#comment-list').children('.inline-comment').hide();
}
}).fail(function(req) {
$('.btn-inline-comment').removeAttr('disabled');
$('#error-content', $form).html($.parseJSON(req.responseText).content);
});
})
</script>
}

View File

@@ -2,9 +2,11 @@
commit: util.JGitUtil.CommitInfo, commit: util.JGitUtil.CommitInfo,
branches: List[String], branches: List[String],
tags: List[String], tags: List[String],
comments: List[model.Comment],
repository: service.RepositoryService.RepositoryInfo, repository: service.RepositoryService.RepositoryInfo,
diffs: Seq[util.JGitUtil.DiffInfo], diffs: Seq[util.JGitUtil.DiffInfo],
oldCommitId: Option[String])(implicit context: app.Context) oldCommitId: Option[String],
hasWritePermission: Boolean)(implicit context: app.Context)
@import context._ @import context._
@import view.helpers._ @import view.helpers._
@import util.Implicits._ @import util.Implicits._
@@ -81,7 +83,14 @@
</td> </td>
</tr> </tr>
</table> </table>
@helper.html.diff(diffs, repository, Some(commit.id), oldCommitId, true) @helper.html.diff(diffs, repository, Some(commit.id), oldCommitId, true, hasWritePermission, true)
<label class="checkbox">
<input type="checkbox" id="show-notes"> Show line notes below
</label>
<div id="comment-list">
@issues.html.commentlist(None, comments, hasWritePermission, repository, None)
</div>
@commentform(commitId = commitId, hasWritePermission = hasWritePermission, repository = repository)
} }
} }
<script> <script>
@@ -122,6 +131,15 @@ $(function(){
}) })
); );
} }
$('#show-notes').change(function() {
if (this.checked) {
$('.inline-comment').show();
} else {
$('.inline-comment').hide();
$('.diff .inline-comment').show();
}
});
}); });
</script> </script>
<style type="text/css"> <style type="text/css">

View File

@@ -0,0 +1,45 @@
@(content: String, commentId: Int, owner: String, repository: String)(implicit context: app.Context)
@import context._
<span class="error-edit-content-@commentId error"></span>
@helper.html.attached(owner, repository){
<textarea style="width: 635px; height: 100px;" id="edit-content-@commentId">@content</textarea>
}
<div>
<input type="button" class="cancel-comment-@commentId btn btn-small btn-danger" value="Cancel"/>
<input type="button" class="update-comment-@commentId btn btn-small pull-right" value="Update comment"/>
</div>
<script>
$(function(){
var curriedCallback = function($box) {
return function(data){
$('.update-comment-@commentId, .cancel-comment-@commentId', $box).removeAttr('disabled');
$('.commit-commentContent-@commentId').empty().html(data.content);
prettyPrint();
}
}
$(document).on('click', '.update-comment-@commentId', function(){
$box = $(this).closest('.box');
$('.update-comment-@commentId, .cancel-comment-@commentId', $box).attr('disabled', 'disabled');
$.ajax({
url: '@path/@owner/@repository/commit_comments/edit/@commentId',
type: 'POST',
data: {
content : $('#edit-content-@commentId', $box).val()
}
}).done(
curriedCallback($box)
).fail(function(req) {
$('.update-comment-@commentId, .cancel-comment-@commentId', $box).removeAttr('disabled');
$('.error-edit-content-@commentId', $box).text($.parseJSON(req.responseText).content);
});
});
$(document).on('click', '.cancel-comment-@commentId', function(){
$box = $(this).closest('.box');
$('.update-comment-@commentId, .cancel-comment-@commentId', $box).attr('disabled', 'disabled');
$.get('@path/@owner/@repository/commit_comments/_data/@commentId', curriedCallback($box));
return false;
});
});
</script>

View File

@@ -26,7 +26,7 @@
</div> </div>
</li> </li>
</ul> </ul>
@helper.html.diff(diffs, repository, None, None, false) @helper.html.diff(diffs, repository, None, None, false, false, false)
@if(hasWritePermission){ @if(hasWritePermission){
<div> <div>
@if(pageName.isDefined){ @if(pageName.isDefined){

View File

@@ -226,6 +226,10 @@ div.box-header-small {
text-shadow: 0 1px 0 #fff text-shadow: 0 1px 0 #fff
} }
.inline-comment div.box-header-small {
background: #f2f8fa;
}
div.box-content { div.box-content {
background-color: white; background-color: white;
border: 1px solid #d8d8d8; border: 1px solid #d8d8d8;
@@ -860,9 +864,10 @@ div.issue-participants {
margin-left: 50px; margin-left: 50px;
} }
div.issue-comment-box { div.issue-comment-box, div.commit-comment-box {
margin-bottom: 15px; margin-bottom: 15px;
margin-left: 50px; margin-left: 50px;
max-width: 820px;
} }
div.issue-comment-action { div.issue-comment-action {
@@ -966,6 +971,38 @@ td.insert, td.equal, td.delete, td.empty {
width: 50%; width: 50%;
} }
table.diff .add-comment {
position: absolute;
background: blue;
top: 0;
color: white;
padding: 2px;
border: solid 1px blue;
border-radius: 3px;
z-index: 99;
}
table.diff .add-comment:hover {
padding: 4px;
top: -1px;
}
table.diff tbody tr.not-diff {
font-family: '"Helvetica Neue", Helvetica, Arial, sans-serif';
}
tr.not-diff .box {
margin-bottom: 0px;
}
table.diff tbody tr.not-diff:hover {
background-color: #fff;
}
table.diff tbody tr.not-diff:hover td{
background-color: #fff;
}
/****************************************************************************/ /****************************************************************************/
/* Repository Settings */ /* Repository Settings */
/****************************************************************************/ /****************************************************************************/

View File

@@ -35,12 +35,12 @@ $(function(){
prettyPrint(); prettyPrint();
}); });
function displayErrors(data){ function displayErrors(data, elem){
var i = 0; var i = 0;
$.each(data, function(key, value){ $.each(data, function(key, value){
$('#error-' + key.split(".").join("_")).text(value); $('#error-' + key.split(".").join("_"), elem).text(value);
if(i === 0){ if(i === 0){
$('#' + key).focus(); $('#' + key, elem).focus();
} }
i++; i++;
}); });

View File

@@ -1,12 +1,8 @@
$(function(){ $(function(){
$.each($('form[validate=true]'), function(i, form){ $(document).on('submit', 'form[validate=true]', validate);
$(form).submit(validate); $(document).on('click', 'input[formaction]', function(e){
}); var form = $(e.target).parents('form');
$.each($('input[formaction]'), function(i, input){ $(form).attr('action', $(e.target).attr('formaction'));
$(input).click(function(){
var form = $(input).parents('form');
$(form).attr('action', $(input).attr('formaction'));
});
}); });
}); });
@@ -29,7 +25,7 @@ function validate(e){
form.data('validated', false); form.data('validated', false);
} else { } else {
form.data('validated', false); form.data('validated', false);
displayErrors(data); displayErrors(data, form);
} }
}, 'json'); }, 'json');
return false; return false;

View File

@@ -79,6 +79,16 @@ diffview = {
return e; return e;
} }
function addButton (e) {
var b = document.createElement("b");
b.appendChild(document.createTextNode("+"));
b.style.display = "none";
b.className = "add-comment";
e.appendChild(b);
e.style.position = "relative";
return e;
}
var tdata = document.createElement("thead"); var tdata = document.createElement("thead");
var node = document.createElement("tr"); var node = document.createElement("tr");
tdata.appendChild(node); tdata.appendChild(node);
@@ -119,8 +129,8 @@ diffview = {
} }
function addCellsInline (row, tidx, tidx2, textLines, change) { function addCellsInline (row, tidx, tidx2, textLines, change) {
row.appendChild(telt("th", tidx == null ? "" : (tidx + 1).toString())); row.appendChild(ctelt("th", "oldline", tidx == null ? "" : (tidx + 1).toString()));
row.appendChild(telt("th", tidx2 == null ? "" : (tidx2 + 1).toString())); row.appendChild(addButton(ctelt("th", "newline", tidx2 == null ? "" : (tidx2 + 1).toString())));
row.appendChild(ctelt("td", change, textLines[tidx != null ? tidx : tidx2].replace(/\t/g, "\u00a0\u00a0\u00a0\u00a0"))); row.appendChild(ctelt("td", change, textLines[tidx != null ? tidx : tidx2].replace(/\t/g, "\u00a0\u00a0\u00a0\u00a0")));
} }