Compare commits

...

20 Commits
2.6 ... 2.7

Author SHA1 Message Date
Naoki Takezoe
9d56d72611 Update README.md for 2.7 release 2014-12-29 11:32:23 +09:00
Naoki Takezoe
527c91ff9d Fix for #581. Column name and CSS is changed. 2014-12-29 03:24:04 +09:00
Naoki Takezoe
c58c2d6700 Merge pull request #556 from michaeljayt/add-ssh-clone-url
Add SSH clone url option when enabled SSH access
2014-12-29 02:12:18 +09:00
Naoki Takezoe
5518eca952 Merge pull request #581 from mrkm4ntr/fix-comment-in-pr
Fix bug on showing inline comments in Pull Request.
2014-12-29 02:10:57 +09:00
Shintaro Murakami
6e2b67ec0b Fix bug on showing inline comments in Pull Request. 2014-12-28 20:12:32 +09:00
Naoki Takezoe
837b1e44a7 Merge pull request #561 from torutk/rpm_nonroot
Enable contrib init file and RPM's spec file to run on RHEL 6/CentOS 6 a...
2014-12-27 20:55:26 +09:00
Naoki Takezoe
e04c230c6e Merge pull request #580 from HairyFotr/patch-lint
Fix a few issues detected by static analysis
2014-12-27 20:10:02 +09:00
HairyFotr
a01b5a4a59 Fix a few issues detected by static analysis 2014-12-26 15:40:05 +01:00
Naoki Takezoe
427b6ce846 Merge pull request #579 from banjun/fix-editor-preview-NoSuchElementException-enableTaskList
fix repo editor fails to preview
2014-12-22 23:06:46 +09:00
banjun
b7b5af2b72 add enableTaskList to post params for _preview 2014-12-22 19:36:12 +09:00
Naoki Takezoe
39fec57f72 (refs #578)Add migration for repositories which have removed parent or origin repository. 2014-12-22 01:32:15 +09:00
Naoki Takezoe
238dedb6df (refs #578)Clean original and parent repository information when it's deleted 2014-12-21 21:39:20 +09:00
Naoki Takezoe
af091117b7 (refs #577)Remove all HTML tags in Markdown 2014-12-20 01:51:15 +09:00
michaeljayt
ddea4e12f0 Add SSH clone url option when enabled SSH access 2014-12-12 21:58:21 +08:00
Naoki Takezoe
9767903252 (refs #567)Fix condition of repository search for issues. 2014-12-05 02:10:28 +09:00
takezoe
bc75f9f8a2 (refs #564)Fix for repository renaming 2014-11-28 01:42:44 +09:00
takezoe
63627fc1d0 (refs #564)Change the attached files directory to /commens from /issues 2014-11-28 00:20:46 +09:00
Naoki Takezoe
c23985c1a7 Merge pull request #564 from mrkm4ntr/coment-for-diff
(refs #9) Comments for commit and diff
2014-11-28 00:08:59 +09:00
Shintaro Murakami
af58e99dcf (refs #9) Comments for commit and diff 2014-11-26 22:59:52 +09:00
Toru Takahashi
676670e9e3 Enable contrib init file and RPM's spec file to run on RHEL 6/CentOS 6 as non root user. 2014-11-24 11:51:36 +09:00
49 changed files with 1123 additions and 248 deletions

View File

@@ -23,7 +23,6 @@ The current version of GitBucket provides a basic features below:
Following features are not implemented, but we will make them in the future release!
- Comment for the changeset
- Network graph
- Statistics
- Watch / Star
@@ -80,6 +79,11 @@ Run the following commands in `Terminal` to
Release Notes
--------
### 2.7 - 29 Dec 2014
- Comment for commit and diff
- Fix security issue in markdown rendering
- Some bug fix and improvements
### 2.6 - 24 Nov 2014
- Search box at issues and pull requests
- Information from administrator

View File

@@ -0,0 +1,15 @@
# Contrib Notes #
RPM spec file and init script for Red Hat Enterprise Linux 6.x.
To create RPM:
1. Edit `../../gitbucket.conf` to suit.
2. Edit `gitbucket.init` to suit.
3. Edit `gitbucket.spec` to suit.
4. Place `gitbucket.spec` to rpm/SPECS/.
5. Place `gitbucket.init` and `gitbucket.war` to rpm/SOURCES/.
6. Execute `rpmbuild -ba rpm/SPECS/gitbucket.spec`
This rpm runs gitbucket not as root user but as gitbucket user.
This rpm creates user and group named `gitbucket` at installation.
This rpm make chkconfig of gitbucket to be on.

View File

@@ -0,0 +1,108 @@
#!/bin/bash
#
# RedHat: /etc/rc.d/init.d/gitbucket
#
# Starts the GitBucket server
#
# chkconfig: 345 60 40
# description: Run GitBucket server
# processname: java
[ -f /etc/rc.d/init.d/functions ] && source /etc/rc.d/init.d/functions # RedHat
# Default values
GITBUCKET_HOME=/var/lib/gitbucket
GITBUCKET_WAR_FILE=/usr/share/gitbucket/lib/gitbucket.war
# Pull in cq settings
[ -f /etc/sysconfig/gitbucket ] && source /etc/sysconfig/gitbucket # RedHat
[ -f gitbucket.conf ] && source gitbucket.conf # For all systems
# Location of the log and PID file
LOG_FILE=$GITBUCKET_LOG_DIR/run.log
RED='\033[1m\E[37;41m'
GREEN='\033[1m\E[37;42m'
OFF='\E[0m'
RETVAL=0
start() {
echo -n $"Starting GitBucket server: "
START_OPTS=
if [ $GITBUCKET_PORT ]; then
START_OPTS="${START_OPTS} --port=${GITBUCKET_PORT}"
fi
if [ $GITBUCKET_PREFIX ]; then
START_OPTS="${START_OPTS} --prefix=${GITBUCKET_PREFIX}"
fi
if [ $GITBUCKET_HOST ]; then
START_OPTS="${START_OPTS} --host=${GITBUCKET_HOST}"
fi
GITBUCKET_HOME="${GITBUCKET_HOME}" daemon --user=gitbucket java $GITBUCKET_JVM_OPTS -jar $GITBUCKET_WAR_FILE $START_OPTS >>$LOG_FILE 2>&1 &
sleep 3
pgrep -f $GITBUCKET_WAR_FILE >> $LOG_FILE 2>&1
RETVAL=$?
if [ $RETVAL -eq 0 ] ; then
success "Success"
else
failure "Exit code $RETVAL"
fi
echo
return $RETVAL
}
stop() {
echo -n $"Stopping GitBucket server: "
# Run the Java process
pkill -f $GITBUCKET_WAR_FILE >>$LOG_FILE 2>&1
RETVAL=$?
if [ $RETVAL -eq 0 ] ; then
success "GitBucket stopping"
else
failure "GitBucket stopping"
fi
echo
return $RETVAL
}
restart() {
stop
start
}
case "$1" in
start)
start
;;
stop)
stop
;;
restart)
restart
;;
status)
pgrep -f $GITBUCKET_WAR_FILE >> $LOG_FILE 2>&1
RETVAL=$?
if [ $RETVAL -eq 0 ]; then
echo $"GitBucket is running...."
else
echo $"GitBucket is stopped"
fi
;;
*)
echo $"Usage: $0 [start|stop|restart|status]"
RETVAL=2
esac
exit $RETVAL

View File

@@ -1,6 +1,6 @@
Name: gitbucket
Summary: GitHub clone written with Scala.
Version: 1.7
Version: 2.6
Release: 1%{?dist}
License: Apache
URL: https://github.com/takezoe/gitbucket
@@ -26,6 +26,25 @@ GitBucket is the easily installable GitHub clone written with Scala.
%{__install} -m 0644 %{SOURCE2} %{buildroot}%{_sysconfdir}/sysconfig/%{name}
touch %{buildroot}%{_localstatedir}/log/%{name}/run.log
%pre
/usr/sbin/groupadd -r gitbucket &> /dev/null || :
/usr/sbin/useradd -g gitbucket -s /bin/false -r -c "GitBucket GitHub clone" -d %{_sharedstatedir}/%{name} gitbucket &> /dev/null || :
%post
/sbin/chkconfig --add gitbucket
%preun
if [ "$1" = 0 ]; then
/sbin/service gitbucket stop > /dev/null 2>&1
/sbin/chkconfig --del gitbucket
fi
exit 0
%postun
if [ "$1" -ge 1 ]; then
/sbin/service gitbucket restart > /dev/null 2>&1
fi
exit 0
%clean
[ "%{buildroot}" != / ] && %{__rm} -rf "%{buildroot}"
@@ -34,12 +53,28 @@ touch %{buildroot}%{_localstatedir}/log/%{name}/run.log
%files
%defattr(-,root,root,-)
%{_datarootdir}/%{name}/lib/%{name}.war
%{_sysconfdir}/init.d/%{name}
%config %{_sysconfdir}/sysconfig/%{name}
%{_localstatedir}/log/%{name}/run.log
%config %{_sysconfdir}/init.d/%{name}
%config(noreplace) %{_sysconfdir}/sysconfig/%{name}
%attr(0755,gitbucket,gitbucket) %{_sharedstatedir}/%{name}
%attr(0750,gitbucket,gitbucket) %{_localstatedir}/log/%{name}
%changelog
* Mon Nov 24 2014 Toru Takahashi <torutk at gmail.com>
- Version bump to v2.6
* Sun Nov 09 2014 Toru Takahashi <torutk at gmail.com>
- Version bump to v2.5
* Sun Oct 26 2014 Toru Takahashi <torutk at gmail.com>
- Version bump to v2.4.1
* Mon Jul 21 2014 Toru Takahashi <torutk at gmail.com>
- execute as gitbucket user
* Sun Jul 20 2014 Toru Takahashi <torutk at gmail.com>
- Version bump to v2.1.
* Mon Oct 28 2013 Jiri Tyr <jiri_DOT_tyr at gmail.com>
- Version bump to v1.7.

View File

@@ -0,0 +1,18 @@
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,
PULL_REQUEST BOOLEAN 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_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);
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 service.IssuesService._
import service.PullRequestService._
import util.JGitUtil.DiffInfo
import util.JGitUtil.CommitInfo
import org.slf4j.LoggerFactory
import org.eclipse.jgit.merge.MergeStrategy
import org.eclipse.jgit.errors.NoMergeBaseException
import service.WebHookService.WebHookPayload
import util.JGitUtil.DiffInfo
import scala.Some
import util.JGitUtil.CommitInfo
class PullRequestsController extends PullRequestsControllerBase
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 {
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])
@@ -81,7 +79,8 @@ trait PullRequestsControllerBase extends ControllerBase {
pulls.html.pullreq(
issue, pullreq,
getComments(owner, name, issueId),
(commits.flatten.map(commit => getCommitComments(owner, name, commit.id, true)).flatten.toList ::: getComments(owner, name, issueId))
.sortWith((a, b) => a.registeredDate before b.registeredDate),
getIssueLabels(owner, name, issueId),
(getCollaborators(owner, name) ::: (if(getAccountByUserName(owner).get.isGroupAccount) Nil else List(owner))).sorted,
getMilestonesWithIssueCount(owner, name),
@@ -281,6 +280,7 @@ trait PullRequestsControllerBase extends ControllerBase {
case (Some(userName), Some(repositoryName)) => (userName, repositoryName) :: getForkedRepositories(userName, repositoryName)
case _ => (forkedRepository.owner, forkedRepository.name) :: getForkedRepositories(forkedRepository.owner, forkedRepository.name)
},
commits.flatten.map(commit => getCommitComments(forkedRepository.owner, forkedRepository.name, commit.id, false)).flatten.toList,
originBranch,
forkedBranch,
oldId.getName,

View File

@@ -20,16 +20,16 @@ import org.eclipse.jgit.revwalk.RevCommit
import service.WebHookService.WebHookPayload
class RepositoryViewerController extends RepositoryViewerControllerBase
with RepositoryService with AccountService with ActivityService with IssuesService with WebHookService
with ReferrerAuthenticator with CollaboratorsAuthenticator
with RepositoryService with AccountService with ActivityService with IssuesService with WebHookService with CommitsService
with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator
/**
* The repository viewer.
*/
trait RepositoryViewerControllerBase extends ControllerBase {
self: RepositoryService with AccountService with ActivityService with IssuesService with WebHookService
with ReferrerAuthenticator with CollaboratorsAuthenticator =>
self: RepositoryService with AccountService with ActivityService with IssuesService with WebHookService with CommitsService
with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator =>
ArchiveCommand.registerFormat("zip", new ZipFormat)
ArchiveCommand.registerFormat("tar.gz", new TgzFormat)
@@ -52,6 +52,14 @@ trait RepositoryViewerControllerBase extends ControllerBase {
fileName: String
)
case class CommentForm(
fileName: Option[String],
oldLineNumber: Option[Int],
newLineNumber: Option[Int],
content: String,
pullRequest: Boolean
)
val editorForm = mapping(
"branch" -> trim(label("Branch", text(required))),
"path" -> trim(label("Path", text())),
@@ -70,6 +78,14 @@ trait RepositoryViewerControllerBase extends ControllerBase {
"fileName" -> trim(label("Filename", text(required)))
)(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))),
"pullRequest" -> trim(label("In pull request", boolean()))
)(CommentForm.apply)
/**
* Returns converted HTML from Markdown for preview.
*/
@@ -221,12 +237,82 @@ trait RepositoryViewerControllerBase extends ControllerBase {
repo.html.commit(id, new JGitUtil.CommitInfo(revCommit),
JGitUtil.getBranchesOfCommit(git, revCommit.getName),
JGitUtil.getTagsOfCommit(git, revCommit.getName),
repository, diffs, oldCommitId)
getCommitComments(repository.owner, repository.name, id, false),
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, form.pullRequest)
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)}
val pullRequest = params.get("pullRequest")
repo.html.commentform(
commitId = id,
fileName, oldLineNumber, newLineNumber, pullRequest.map(_.toBoolean).getOrElse(false),
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, form.pullRequest)
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.
*/
@@ -461,4 +547,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
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)
}
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,78 @@
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")
val pullRequest = column[Boolean]("PULL_REQUEST")
def * = (userName, repositoryName, commitId, commentId, commentedUserName, content, fileName, oldLine, newLine, registeredDate, updatedDate, pullRequest) <> (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,
pullRequest: Boolean
) 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 ActivityComponent
with CollaboratorComponent
with CommitCommentComponent
with GroupMemberComponent
with IssueComponent
with IssueCommentComponent

View File

@@ -98,8 +98,8 @@ object PluginSystem extends PluginService {
// Compile and eval Scala source code
ScalaPlugin.eval(pluginDir.listFiles.filter(_.getName.endsWith(".scala.html")).map { file =>
ScalaPlugin.compileTemplate(
id.replaceAll("-", ""),
file.getName.replaceAll("\\.scala\\.html$", ""),
id.replace("-", ""),
file.getName.stripSuffix(".scala.html"),
IOUtils.toString(new FileInputStream(file)))
}.mkString("\n") + source)

View File

@@ -95,6 +95,15 @@ trait ActivityService {
Some(cut(comment, 200)),
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)
(implicit s: Session): Unit =
Activities insert Activity(userName, repositoryName, activityUserName,

View File

@@ -0,0 +1,52 @@
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, pullRequest: Boolean)(implicit s: Session) =
CommitComments filter {
t => t.byCommit(owner, repository, commitId) && (t.pullRequest === pullRequest || pullRequest)
} 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], pullRequest: Boolean)(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,
pullRequest = pullRequest)
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

@@ -263,6 +263,7 @@ trait IssuesService {
// Search Issue
val issues = Issues
.filter(_.byRepository(owner, repository))
.innerJoin(IssueOutline).on { case (t1, t2) =>
t1.byIssue(t2.userName, t2.repositoryName, t2.issueId)
}
@@ -278,6 +279,7 @@ trait IssuesService {
// Search IssueComment
val comments = IssueComments
.filter(_.byRepository(owner, repository))
.innerJoin(Issues).on { case (t1, t2) =>
t1.byIssue(t2.userName, t2.repositoryName, t2.issueId)
}
@@ -419,7 +421,7 @@ object IssuesService {
conditions.get("author").flatMap(_.headOption),
conditions.get("assignee").flatMap(_.headOption),
conditions.get("mentions").flatMap(_.headOption),
conditions.get("is").getOrElse(Seq.empty).filter(x => x == "open" || x == "closed").headOption.getOrElse("open"),
conditions.get("is").getOrElse(Seq.empty).find(x => x == "open" || x == "closed").getOrElse("open"),
sort,
direction,
conditions.get("visibility").flatMap(_.headOption),

View File

@@ -20,6 +20,7 @@ trait RepositorySearchService { self: IssuesService =>
searchIssuesByKeyword(owner, repository, query).map { case (issue, commentCount, content) =>
IssueSearchResult(
issue.issueId,
issue.isPullRequest,
issue.title,
issue.openedUserName,
issue.registeredDate,
@@ -111,6 +112,7 @@ object RepositorySearchService {
case class IssueSearchResult(
issueId: Int,
isPullRequest: Boolean,
title: String,
openedUserName: String,
registeredDate: java.util.Date,

View File

@@ -46,15 +46,16 @@ trait RepositoryService { self: AccountService =>
(Repositories filter { t => t.byRepository(oldUserName, oldRepositoryName) } firstOption).map { repository =>
Repositories insert repository.copy(userName = newUserName, repositoryName = newRepositoryName)
val webHooks = WebHooks .filter(_.byRepository(oldUserName, oldRepositoryName)).list
val milestones = Milestones .filter(_.byRepository(oldUserName, oldRepositoryName)).list
val issueId = IssueId .filter(_.byRepository(oldUserName, oldRepositoryName)).list
val issues = Issues .filter(_.byRepository(oldUserName, oldRepositoryName)).list
val pullRequests = PullRequests .filter(_.byRepository(oldUserName, oldRepositoryName)).list
val labels = Labels .filter(_.byRepository(oldUserName, oldRepositoryName)).list
val issueComments = IssueComments.filter(_.byRepository(oldUserName, oldRepositoryName)).list
val issueLabels = IssueLabels .filter(_.byRepository(oldUserName, oldRepositoryName)).list
val collaborators = Collaborators.filter(_.byRepository(oldUserName, oldRepositoryName)).list
val webHooks = WebHooks .filter(_.byRepository(oldUserName, oldRepositoryName)).list
val milestones = Milestones .filter(_.byRepository(oldUserName, oldRepositoryName)).list
val issueId = IssueId .filter(_.byRepository(oldUserName, oldRepositoryName)).list
val issues = Issues .filter(_.byRepository(oldUserName, oldRepositoryName)).list
val pullRequests = PullRequests .filter(_.byRepository(oldUserName, oldRepositoryName)).list
val labels = Labels .filter(_.byRepository(oldUserName, oldRepositoryName)).list
val issueComments = IssueComments .filter(_.byRepository(oldUserName, oldRepositoryName)).list
val issueLabels = IssueLabels .filter(_.byRepository(oldUserName, oldRepositoryName)).list
val commitComments = CommitComments.filter(_.byRepository(oldUserName, oldRepositoryName)).list
val collaborators = Collaborators .filter(_.byRepository(oldUserName, oldRepositoryName)).list
Repositories.filter { t =>
(t.originUserName === oldUserName.bind) && (t.originRepositoryName === oldRepositoryName.bind)
@@ -94,6 +95,7 @@ trait RepositoryService { self: AccountService =>
IssueComments .insertAll(issueComments .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
Labels .insertAll(labels .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
IssueLabels .insertAll(issueLabels .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
CommitComments.insertAll(commitComments.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
if(account.isGroupAccount){
Collaborators.insertAll(getGroupMembers(newUserName).map(m => Collaborator(newUserName, newRepositoryName, m.userName)) :_*)
@@ -103,7 +105,9 @@ trait RepositoryService { self: AccountService =>
// Update activity messages
Activities.filter { t =>
(t.message like s"%:${oldUserName}/${oldRepositoryName}]%") || (t.message like s"%:${oldUserName}/${oldRepositoryName}#%")
(t.message like s"%:${oldUserName}/${oldRepositoryName}]%") ||
(t.message like s"%:${oldUserName}/${oldRepositoryName}#%") ||
(t.message like s"%:${oldUserName}/${oldRepositoryName}@%")
}.map { t => t.activityId -> t.message }.list.foreach { case (activityId, message) =>
Activities.filter(_.activityId === activityId.bind).map(_.message).update(
message
@@ -112,6 +116,7 @@ trait RepositoryService { self: AccountService =>
.replace(s"[tag:${oldUserName}/${oldRepositoryName}#" ,s"[tag:${newUserName}/${newRepositoryName}#")
.replace(s"[pullreq:${oldUserName}/${oldRepositoryName}#",s"[pullreq:${newUserName}/${newRepositoryName}#")
.replace(s"[issue:${oldUserName}/${oldRepositoryName}#" ,s"[issue:${newUserName}/${newRepositoryName}#")
.replace(s"[commit:${oldUserName}/${oldRepositoryName}@" ,s"[commit:${newUserName}/${newRepositoryName}@")
)
}
}
@@ -121,6 +126,7 @@ trait RepositoryService { self: AccountService =>
def deleteRepository(userName: String, repositoryName: String)(implicit s: Session): Unit = {
Activities .filter(_.byRepository(userName, repositoryName)).delete
Collaborators .filter(_.byRepository(userName, repositoryName)).delete
CommitComments.filter(_.byRepository(userName, repositoryName)).delete
IssueLabels .filter(_.byRepository(userName, repositoryName)).delete
Labels .filter(_.byRepository(userName, repositoryName)).delete
IssueComments .filter(_.byRepository(userName, repositoryName)).delete
@@ -130,6 +136,30 @@ trait RepositoryService { self: AccountService =>
Milestones .filter(_.byRepository(userName, repositoryName)).delete
WebHooks .filter(_.byRepository(userName, repositoryName)).delete
Repositories .filter(_.byRepository(userName, repositoryName)).delete
// Update ORIGIN_USER_NAME and ORIGIN_REPOSITORY_NAME
Repositories
.filter { x => (x.originUserName === userName.bind) && (x.originRepositoryName === repositoryName.bind) }
.map { x => (x.userName, x.repositoryName) }
.list
.foreach { case (userName, repositoryName) =>
Repositories
.filter(_.byRepository(userName, repositoryName))
.map(x => (x.originUserName?, x.originRepositoryName?))
.update(None, None)
}
// Update PARENT_USER_NAME and PARENT_REPOSITORY_NAME
Repositories
.filter { x => (x.parentUserName === userName.bind) && (x.parentRepositoryName === repositoryName.bind) }
.map { x => (x.userName, x.repositoryName) }
.list
.foreach { case (userName, repositoryName) =>
Repositories
.filter(_.byRepository(userName, repositoryName))
.map(x => (x.parentUserName?, x.parentRepositoryName?))
.update(None, None)
}
}
/**
@@ -160,7 +190,7 @@ trait RepositoryService { self: AccountService =>
JGitUtil.getRepositoryInfo(repository.userName, repository.repositoryName, baseUrl),
repository,
issues.size,
issues.filter(_ == true).size,
issues.count(_ == true),
getForkedCount(
repository.originUserName.getOrElse(repository.userName),
repository.originRepositoryName.getOrElse(repository.repositoryName)
@@ -358,4 +388,4 @@ object RepositoryService {
case class RepositoryTreeNode(owner: String, name: String, children: List[RepositoryTreeNode])
}
}

View File

@@ -40,7 +40,7 @@ trait SystemSettingsService {
props.setProperty(LdapUserNameAttribute, ldap.userNameAttribute)
ldap.additionalFilterCondition.foreach(x => props.setProperty(LdapAdditionalFilterCondition, x))
ldap.fullNameAttribute.foreach(x => props.setProperty(LdapFullNameAttribute, x))
ldap.mailAttribute.foreach(x => props.setProperty(LdapMailAddressAttribute, x.toString))
ldap.mailAttribute.foreach(x => props.setProperty(LdapMailAddressAttribute, x))
ldap.tls.foreach(x => props.setProperty(LdapTls, x.toString))
ldap.keystore.foreach(x => props.setProperty(LdapKeystore, x))
}

View File

@@ -8,6 +8,7 @@ import org.apache.commons.io.IOUtils
import org.slf4j.LoggerFactory
import util.Directory._
import util.ControlUtil._
import util.JDBCUtil._
import org.eclipse.jgit.api.Git
import util.Directory
import plugin.PluginUpdateJob
@@ -53,24 +54,53 @@ object AutoUpdate {
* The history of versions. A head of this sequence is the current BitBucket version.
*/
val versions = Seq(
new Version(2, 7) {
override def update(conn: Connection): Unit = {
super.update(conn)
conn.select("SELECT * FROM REPOSITORY"){ rs =>
// Rename attached files directory from /issues to /comments
val userName = rs.getString("USER_NAME")
val repoName = rs.getString("REPOSITORY_NAME")
defining(Directory.getAttachedDir(userName, repoName)){ newDir =>
val oldDir = new File(newDir.getParentFile, "issues")
if(oldDir.exists && oldDir.isDirectory){
oldDir.renameTo(newDir)
}
}
// Update ORIGIN_USER_NAME and ORIGIN_REPOSITORY_NAME if it does not exist
val originalUserName = rs.getString("ORIGIN_USER_NAME")
val originalRepoName = rs.getString("ORIGIN_REPOSITORY_NAME")
if(originalUserName != null && originalRepoName != null){
if(conn.selectInt("SELECT COUNT(*) FROM REPOSITORY WHERE USER_NAME = ? AND REPOSITORY_NAME = ?",
originalUserName, originalRepoName) == 0){
conn.update("UPDATE REPOSITORY SET ORIGIN_USER_NAME = NULL, ORIGIN_REPOSITORY_NAME = NULL " +
"WHERE USER_NAME = ? AND REPOSITORY_NAME = ?", userName, repoName)
}
}
// Update PARENT_USER_NAME and PARENT_REPOSITORY_NAME if it does not exist
val parentUserName = rs.getString("PARENT_USER_NAME")
val parentRepoName = rs.getString("PARENT_REPOSITORY_NAME")
if(parentUserName != null && parentRepoName != null){
if(conn.selectInt("SELECT COUNT(*) FROM REPOSITORY WHERE USER_NAME = ? AND REPOSITORY_NAME = ?",
parentUserName, parentRepoName) == 0){
conn.update("UPDATE REPOSITORY SET PARENT_USER_NAME = NULL, PARENT_REPOSITORY_NAME = NULL " +
"WHERE USER_NAME = ? AND REPOSITORY_NAME = ?", userName, repoName)
}
}
}
}
},
new Version(2, 6),
new Version(2, 5),
new Version(2, 4),
new Version(2, 3) {
override def update(conn: Connection): Unit = {
super.update(conn)
using(conn.createStatement.executeQuery("SELECT ACTIVITY_ID, ADDITIONAL_INFO FROM ACTIVITY WHERE ACTIVITY_TYPE='push'")){ rs =>
while(rs.next) {
val info = rs.getString("ADDITIONAL_INFO")
val newInfo = info.split("\n").filter(_ matches "^[0-9a-z]{40}:.*").mkString("\n")
if (info != newInfo) {
val id = rs.getString("ACTIVITY_ID")
using(conn.prepareStatement("UPDATE ACTIVITY SET ADDITIONAL_INFO=? WHERE ACTIVITY_ID=?")) { sql =>
sql.setString(1, newInfo)
sql.setLong(2, id.toLong)
sql.executeUpdate
}
}
conn.select("SELECT ACTIVITY_ID, ADDITIONAL_INFO FROM ACTIVITY WHERE ACTIVITY_TYPE='push'"){ rs =>
val curInfo = rs.getString("ADDITIONAL_INFO")
val newInfo = curInfo.split("\n").filter(_ matches "^[0-9a-z]{40}:.*").mkString("\n")
if (curInfo != newInfo) {
conn.update("UPDATE ACTIVITY SET ADDITIONAL_INFO = ? WHERE ACTIVITY_ID = ?", newInfo, rs.getInt("ACTIVITY_ID"))
}
}
FileUtils.deleteDirectory(Directory.getPluginCacheDir())
@@ -87,16 +117,14 @@ object AutoUpdate {
mimeUtil.registerMimeDetector("eu.medsea.mimeutil.detector.MagicMimeMimeDetector")
super.update(conn)
using(conn.createStatement.executeQuery("SELECT USER_NAME, REPOSITORY_NAME FROM REPOSITORY")){ rs =>
while(rs.next){
defining(Directory.getAttachedDir(rs.getString("USER_NAME"), rs.getString("REPOSITORY_NAME"))){ dir =>
if(dir.exists && dir.isDirectory){
dir.listFiles.foreach { file =>
if(file.getName.indexOf('.') < 0){
val mimeType = MimeUtil2.getMostSpecificMimeType(mimeUtil.getMimeTypes(file, new MimeType("application/octet-stream"))).toString
if(mimeType.startsWith("image/")){
file.renameTo(new File(file.getParent, file.getName + "." + mimeType.split("/")(1)))
}
conn.select("SELECT USER_NAME, REPOSITORY_NAME FROM REPOSITORY"){ rs =>
defining(Directory.getAttachedDir(rs.getString("USER_NAME"), rs.getString("REPOSITORY_NAME"))){ dir =>
if(dir.exists && dir.isDirectory){
dir.listFiles.foreach { file =>
if(file.getName.indexOf('.') < 0){
val mimeType = MimeUtil2.getMostSpecificMimeType(mimeUtil.getMimeTypes(file, new MimeType("application/octet-stream"))).toString
if(mimeType.startsWith("image/")){
file.renameTo(new File(file.getParent, file.getName + "." + mimeType.split("/")(1)))
}
}
}
@@ -119,14 +147,12 @@ object AutoUpdate {
override def update(conn: Connection): Unit = {
super.update(conn)
// Fix wiki repository configuration
using(conn.createStatement.executeQuery("SELECT USER_NAME, REPOSITORY_NAME FROM REPOSITORY")){ rs =>
while(rs.next){
using(Git.open(getWikiRepositoryDir(rs.getString("USER_NAME"), rs.getString("REPOSITORY_NAME")))){ git =>
defining(git.getRepository.getConfig){ config =>
if(!config.getBoolean("http", "receivepack", false)){
config.setBoolean("http", null, "receivepack", true)
config.save
}
conn.select("SELECT USER_NAME, REPOSITORY_NAME FROM REPOSITORY"){ rs =>
using(Git.open(getWikiRepositoryDir(rs.getString("USER_NAME"), rs.getString("REPOSITORY_NAME")))){ git =>
defining(git.getRepository.getConfig){ config =>
if(!config.getBoolean("http", "receivepack", false)){
config.setBoolean("http", null, "receivepack", true)
config.save
}
}
}

View File

@@ -48,7 +48,7 @@ object Directory {
* Directory for files which are attached to issue.
*/
def getAttachedDir(owner: String, repository: String): File =
new File(s"${RepositoryHome}/${owner}/${repository}/issues")
new File(s"${RepositoryHome}/${owner}/${repository}/comments")
/**
* Directory for uploaded files by the specified user.

View File

@@ -0,0 +1,55 @@
package util
import java.sql._
import util.ControlUtil._
import scala.collection.mutable.ListBuffer
/**
* Provides implicit class which extends java.sql.Connection.
* This is used in automatic migration in [[servlet.AutoUpdateListener]].
*/
object JDBCUtil {
implicit class RichConnection(conn: Connection){
def update(sql: String, params: Any*): Int = {
execute(sql, params: _*){ stmt =>
stmt.executeUpdate()
}
}
def select[T](sql: String, params: Any*)(f: ResultSet => T): Seq[T] = {
execute(sql, params: _*){ stmt =>
using(stmt.executeQuery()){ rs =>
val list = new ListBuffer[T]
while(rs.next){
list += f(rs)
}
list.toSeq
}
}
}
def selectInt(sql: String, params: Any*): Int = {
execute(sql, params: _*){ stmt =>
using(stmt.executeQuery()){ rs =>
if(rs.next) rs.getInt(1) else 0
}
}
}
private def execute[T](sql: String, params: Any*)(f: (PreparedStatement) => T): T = {
using(conn.prepareStatement(sql)){ stmt =>
params.zipWithIndex.foreach { case (p, i) =>
p match {
case x: Int => stmt.setInt(i + 1, x)
case x: String => stmt.setString(i + 1, x)
}
}
f(stmt)
}
}
}
}

View File

@@ -217,7 +217,7 @@ object JGitUtil {
list.append((treeWalk.getObjectId(0), treeWalk.getFileMode(0), treeWalk.getPathString, treeWalk.getNameString, linkUrl))
}
list = list.map(tuple =>
list.transform(tuple =>
if (tuple._2 != FileMode.TREE)
tuple
else

View File

@@ -12,7 +12,7 @@ trait LinkConverter { self: RequestCache =>
issueIdPrefix: String = "#")(implicit context: app.Context): String = {
value
// escape HTML tags
.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;")
.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").replace("\"", "&quot;")
// convert issue id to link
.replaceBy(("(?<=(^|\\W))" + issueIdPrefix + "([0-9]+)(?=(\\W|$))").r){ m =>
getIssue(repository.owner, repository.name, m.group(2)) match {

View File

@@ -32,7 +32,7 @@ object Markdown {
} else s
val rootNode = new PegDownProcessor(
Extensions.AUTOLINKS | Extensions.WIKILINKS | Extensions.FENCED_CODE_BLOCKS | Extensions.TABLES | Extensions.HARDWRAPS
Extensions.AUTOLINKS | Extensions.WIKILINKS | Extensions.FENCED_CODE_BLOCKS | Extensions.TABLES | Extensions.HARDWRAPS | Extensions.SUPPRESS_ALL_HTML
).parseMarkdown(source.toCharArray)
new GitBucketHtmlSerializer(markdown, repository, enableWikiLink, enableRefsLink, enableTaskList, hasWritePermission).toHtml(rootNode)

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("\\[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("\\[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 {
case "open_issue" => detailActivity(activity, "activity-issue.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 "reopen_issue" => detailActivity(activity, "activity-issue-reopen.png")
case "open_pullreq" => detailActivity(activity, "activity-merge.png")

View File

@@ -7,18 +7,24 @@
@defining("(id=\")([\\w\\-]*)(\")".r.findFirstMatchIn(textarea.body).map(_.group(2))){ textareaId =>
<script>
$(function(){
$([$('#@textareaId').closest('div')[0], $('#@textareaId').next('div')[0]]).dropzone({
url: '@path/upload/image/@owner/@repository',
maxFilesize: 10,
acceptedFiles: 'image/*',
dictInvalidFileType: 'Unfortunately, we don\'t support that file type. Try again with a PNG, GIF, or JPG.',
previewTemplate: "<div class=\"dz-preview\">\n <div class=\"dz-progress\"><span class=\"dz-upload\" data-dz-uploadprogress>Uploading your images...</span></div>\n <div class=\"dz-error-message\"><span data-dz-errormessage></span></div>\n</div>",
success: function(file, id) {
var images = '\n![' + file.name.split('.')[0] + '](@baseUrl/@owner/@repository/_attached/' + id + ')';
$('#@textareaId').val($('#@textareaId').val() + images);
$(file.previewElement).prevAll('div.dz-preview').addBack().remove();
try {
$([$('#@textareaId').closest('div')[0], $('#@textareaId').next('div')[0]]).dropzone({
url: '@path/upload/image/@owner/@repository',
maxFilesize: 10,
acceptedFiles: 'image/*',
dictInvalidFileType: 'Unfortunately, we don\'t support that file type. Try again with a PNG, GIF, or JPG.',
previewTemplate: "<div class=\"dz-preview\">\n <div class=\"dz-progress\"><span class=\"dz-upload\" data-dz-uploadprogress>Uploading your images...</span></div>\n <div class=\"dz-error-message\"><span data-dz-errormessage></span></div>\n</div>",
success: function(file, id) {
var images = '\n![' + file.name.split('.')[0] + '](@baseUrl/@owner/@repository/_attached/' + id + ')';
$('#@textareaId').val($('#@textareaId').val() + images);
$(file.previewElement).prevAll('div.dz-preview').addBack().remove();
}
});
} catch(e) {
if (e.message !== "Dropzone already attached.") {
throw e;
}
});
}
// Adjust clickable area width
$('#@textareaId').next('div.clickable').css('width', ($('#@textareaId').width() + 8) + 'px');

View File

@@ -0,0 +1,35 @@
@(comment: model.CommitComment,
hasWritePermission: Boolean,
repository: service.RepositoryService.RepositoryInfo,
latestCommitId: Option[String] = None)(implicit context: app.Context)
@import context._
@import view.helpers._
<div class="@if(comment.fileName.isDefined && (!latestCommitId.isDefined || latestCommitId.get == comment.commitId)){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.pullRequest){
on this Pull Request
}else{
@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

@@ -1,8 +1,11 @@
@(diffs: Seq[util.JGitUtil.DiffInfo],
repository: service.RepositoryService.RepositoryInfo,
newCommitId: Option[String],
oldCommitId: Option[String],
showIndex: Boolean)(implicit context: app.Context)
repository: service.RepositoryService.RepositoryInfo,
newCommitId: Option[String],
oldCommitId: Option[String],
showIndex: Boolean,
pullRequest: Boolean,
hasWritePermission: Boolean,
showLineNotes: Boolean)(implicit context: app.Context)
@import context._
@import view.helpers._
@import org.eclipse.jgit.diff.DiffEntry.ChangeType
@@ -39,7 +42,7 @@
}
@diffs.zipWithIndex.map { case (diff, i) =>
<a name="diff-@i"></a>
<table class="table table-bordered">
<table class="table table-bordered" commitId="@newCommitId" fileName="@diff.newPath">
<tr>
<th style="font-weight: normal; line-height: 27px;" class="box-header">
@if(diff.changeType == ChangeType.COPY || diff.changeType == ChangeType.RENAME){
@@ -118,6 +121,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 + '&pullRequest=@pullRequest';
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-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', {
content : $('#content').val(),
enableWikiLink : @enableWikiLink,

View File

@@ -1,107 +1,115 @@
@(issue: model.Issue,
comments: List[model.IssueComment],
@(issue: Option[model.Issue],
comments: List[model.Comment],
hasWritePermission: Boolean,
repository: service.RepositoryService.RepositoryInfo,
pullreq: Option[model.PullRequest] = None)(implicit context: app.Context)
@import context._
@import view.helpers._
<div class="issue-avatar-image">@avatar(issue.openedUserName, 48)</div>
<div class="box issue-comment-box">
<div class="box-header-small">
@user(issue.openedUserName, styleClass="username strong") <span class="muted">commented @helper.html.datetimeago(issue.registeredDate)</span>
<span class="pull-right">
@if(hasWritePermission || loginAccount.map(_.userName == issue.openedUserName).getOrElse(false)){
<a href="#" data-issue-id="@issue.issueId"><i class="icon-pencil"></i></a>
}
</span>
@if(issue.isDefined){
<div class="issue-avatar-image">@avatar(issue.get.openedUserName, 48)</div>
<div class="box issue-comment-box">
<div class="box-header-small">
@user(issue.get.openedUserName, styleClass="username strong") <span class="muted">commented @helper.html.datetimeago(issue.get.registeredDate)</span>
<span class="pull-right">
@if(hasWritePermission || loginAccount.map(_.userName == issue.get.openedUserName).getOrElse(false)){
<a href="#" data-issue-id="@issue.get.issueId"><i class="icon-pencil"></i></a>
}
</span>
</div>
<div class="box-content issue-content" id="issueContent">
@markdown(issue.get.content getOrElse "No description provided.", repository, false, true, true, hasWritePermission)
</div>
</div>
<div class="box-content issue-content" id="issueContent">
@markdown(issue.content getOrElse "No description provided.", repository, false, true, true, hasWritePermission)
</div>
</div>
}
@comments.map { comment =>
@if(comment.action != "close" && comment.action != "reopen" && comment.action != "delete_branch"){
<div class="issue-avatar-image">@avatar(comment.commentedUserName, 48)</div>
<div class="box issue-comment-box" id="comment-@comment.commentId">
<div class="box-header-small">
@user(comment.commentedUserName, styleClass="username strong")
<span class="muted">
@if(comment.action == "comment"){
commented
} else {
@if(pullreq.isEmpty){ referenced the issue } else { referenced the pull request }
}
@helper.html.datetimeago(comment.registeredDate)
</span>
<span class="pull-right">
@if(comment.action != "commit" && comment.action != "merge" && comment.action != "refer" &&
(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"class="issue-content" id="commentContent-@comment.commentId">
@if(comment.action == "commit" && comment.content.split(" ").last.matches("[a-f0-9]{40}")){
@defining(comment.content.substring(comment.content.length - 40)){ id =>
<div class="pull-right"><a href="@path/@repository.owner/@repository.name/commit/@id" class="monospace">@id.substring(0, 7)</a></div>
@markdown(comment.content.substring(0, comment.content.length - 41), repository, false, true, true, hasWritePermission)
}
} else {
@if(comment.action == "refer"){
@defining(comment.content.split(":")){ case Array(issueId, rest @ _*) =>
<strong><a href="@path/@repository.owner/@repository.name/issues/@issueId">Issue #@issueId</a>: @rest.mkString(":")</strong>
@comments.map {
case comment: model.IssueComment => {
@if(comment.action != "close" && comment.action != "reopen" && comment.action != "delete_branch"){
<div class="issue-avatar-image">@avatar(comment.commentedUserName, 48)</div>
<div class="box issue-comment-box" id="comment-@comment.commentId">
<div class="box-header-small">
@user(comment.commentedUserName, styleClass="username strong")
<span class="muted">
@if(comment.action == "comment"){
commented
} else {
@if(pullreq.isEmpty){ referenced the issue } else { referenced the pull request }
}
@helper.html.datetimeago(comment.registeredDate)
</span>
<span class="pull-right">
@if(comment.action != "commit" && comment.action != "merge" && comment.action != "refer"
&& (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"class="issue-content" id="commentContent-@comment.commentId">
@if(comment.action == "commit" && comment.content.split(" ").last.matches("[a-f0-9]{40}")){
@defining(comment.content.substring(comment.content.length - 40)){ id =>
<div class="pull-right"><a href="@path/@repository.owner/@repository.name/commit/@id" class="monospace">@id.substring(0, 7)</a></div>
@markdown(comment.content.substring(0, comment.content.length - 41), repository, false, true, true, hasWritePermission)
}
} else {
@markdown(comment.content, repository, false, true, true, hasWritePermission)
@if(comment.action == "refer"){
@defining(comment.content.split(":")){ case Array(issueId, rest @ _*) =>
<strong><a href="@path/@repository.owner/@repository.name/issues/@issueId">Issue #@issueId</a>: @rest.mkString(":")</strong>
}
} else {
@markdown(comment.content, repository, false, true, true, hasWritePermission)
}
}
</div>
</div>
}
@if(comment.action == "merge"){
<div class="small" style="margin-top: 10px; margin-bottom: 10px;">
<span class="label label-info">Merged</span>
@avatar(comment.commentedUserName, 20)
@user(comment.commentedUserName, styleClass="username strong") merged commit <code>@pullreq.map(_.commitIdTo.substring(0, 7))</code> into
@if(pullreq.get.requestUserName == repository.owner){
<span class="label label-info monospace">@pullreq.map(_.branch)</span> from <span class="label label-info monospace">@pullreq.map(_.requestBranch)</span>
} else {
<span class="label label-info monospace">@pullreq.map(_.userName):@pullreq.map(_.branch)</span> from <span class="label label-info monospace">@pullreq.map(_.requestUserName):@pullreq.map(_.requestBranch)</span>
}
@helper.html.datetimeago(comment.registeredDate)
</div>
}
@if(comment.action == "close" || comment.action == "close_comment"){
<div class="small issue-comment-action">
<span class="label label-important">Closed</span>
@avatar(comment.commentedUserName, 20)
@if(issue.isDefined && issue.get.isPullRequest){
@user(comment.commentedUserName, styleClass="username strong") closed the pull request @helper.html.datetimeago(comment.registeredDate)
} else {
@user(comment.commentedUserName, styleClass="username strong") closed the issue @helper.html.datetimeago(comment.registeredDate)
}
</div>
</div>
}
@if(comment.action == "reopen" || comment.action == "reopen_comment"){
<div class="small issue-comment-action">
<span class="label label-success">Reopened</span>
@avatar(comment.commentedUserName, 20)
@user(comment.commentedUserName, styleClass="username strong") reopened the issue @helper.html.datetimeago(comment.registeredDate)
</div>
}
@if(comment.action == "delete_branch"){
<div class="small issue-comment-action">
<span class="label">Deleted</span>
@avatar(comment.commentedUserName, 20)
@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>
}
}
@if(comment.action == "merge"){
<div class="small" style="margin-top: 10px; margin-bottom: 10px;">
<span class="label label-info">Merged</span>
@avatar(comment.commentedUserName, 20)
@user(comment.commentedUserName, styleClass="username strong") merged commit <code>@pullreq.map(_.commitIdTo.substring(0, 7))</code> into
@if(pullreq.get.requestUserName == repository.owner){
<span class="label label-info monospace">@pullreq.map(_.branch)</span> from <span class="label label-info monospace">@pullreq.map(_.requestBranch)</span>
} else {
<span class="label label-info monospace">@pullreq.map(_.userName):@pullreq.map(_.branch)</span> from <span class="label label-info monospace">@pullreq.map(_.requestUserName):@pullreq.map(_.requestBranch)</span>
}
@helper.html.datetimeago(comment.registeredDate)
</div>
}
@if(comment.action == "close" || comment.action == "close_comment"){
<div class="small issue-comment-action">
<span class="label label-important">Closed</span>
@avatar(comment.commentedUserName, 20)
@if(issue.isPullRequest){
@user(comment.commentedUserName, styleClass="username strong") closed the pull request @helper.html.datetimeago(comment.registeredDate)
} else {
@user(comment.commentedUserName, styleClass="username strong") closed the issue @helper.html.datetimeago(comment.registeredDate)
}
</div>
}
@if(comment.action == "reopen" || comment.action == "reopen_comment"){
<div class="small issue-comment-action">
<span class="label label-success">Reopened</span>
@avatar(comment.commentedUserName, 20)
@user(comment.commentedUserName, styleClass="username strong") reopened the issue @helper.html.datetimeago(comment.registeredDate)
</div>
}
@if(comment.action == "delete_branch"){
<div class="small issue-comment-action">
<span class="label">Deleted</span>
@avatar(comment.commentedUserName, 20)
@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>
case comment: model.CommitComment => {
@helper.html.commitcomment(comment, hasWritePermission, repository, pullreq.map(_.commitIdTo))
}
}
<script>
$(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 url = '@url(repository)/issue_comments/_data/' + id;
var $content = $('#commentContent-' + id);
@@ -134,6 +142,34 @@ $(function(){
}
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){
$('body').append('<div id="tmp"></div>');
@@ -156,15 +192,40 @@ $(function(){
return ss.join('');
};
$('#issueContent').on('click', ':checkbox', function(ev){
var checkboxes = $('#issueContent :checkbox');
$.get('@url(repository)/issues/_data/@issue.issueId',
$('div[class*=commit-commentContent-]').on('click', ':checkbox', function(ev){
var $commentContent = $(ev.target).parents('div[class*=commit-commentContent-]'),
commentId = $commentContent.attr('class').match(/commit-commentContent-.+/)[0].replace(/commit-commentContent-/, ''),
checkboxes = $commentContent.find(':checkbox');
$.get('@url(repository)/commit_comments/_data/' + commentId,
{
dataType : 'html'
},
function(responseContent){
$.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',
data: {
title : $('#issueTitle').text(),
@@ -196,5 +257,7 @@ $(function(){
);
});
}
});
</script>

View File

@@ -38,7 +38,7 @@
}
<span class="muted">
@user(issue.openedUserName, styleClass="username strong") opened this issue @helper.html.datetimeago(issue.registeredDate) - @defining(
comments.filter( _.action.contains("comment") ).size
comments.count( _.action.contains("comment") )
){ count =>
@count @plural(count, "comment")
}
@@ -47,7 +47,7 @@
<hr>
<div class="row-fluid">
<div class="span10">
@commentlist(issue, comments, hasWritePermission, repository)
@commentlist(Some(issue), comments, hasWritePermission, repository)
@commentform(issue, true, hasWritePermission, repository)
</div>
<div class="span2">
@@ -89,4 +89,4 @@ $(function(){
return false;
});
});
</script>
</script>

View File

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

View File

@@ -14,11 +14,11 @@
<span class="small">
<a class="button-link@if(state == "open"){ selected}" href="?state=open">
<img src="@assets/common/images/milestone@(if(state == "open"){"-active"}).png"/>
@milestones.filter(_._1.closedDate.isEmpty).size Open
@milestones.count(_._1.closedDate.isEmpty) Open
</a>&nbsp;&nbsp;
<a class="button-link@if(state == "closed"){ selected}" href="?state=closed">
<img src="@assets/common/images/milestone@(if(state == "closed"){"-active"}).png"/>
@milestones.filter(_._1.closedDate.isDefined).size Closed
@milestones.count(_._1.closedDate.isDefined) Closed
</a>
</span>
</th>

View File

@@ -1,4 +1,5 @@
@(commits: Seq[Seq[util.JGitUtil.CommitInfo]],
comments: Option[List[model.Comment]] = None,
repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context)
@import context._
@import view.helpers._
@@ -15,6 +16,14 @@
@user(commit.authorName, commit.authorEmailAddress, "username")
</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
}.count(t => t.commitId == commit.id && !t.pullRequest)
}</span>
</td>
<td style="width: 10%; text-align: right;">
<a href="@url(repository)/commit/@commit.id" class="monospace">@commit.id.substring(0, 7)</a>
</td>

View File

@@ -1,6 +1,7 @@
@(commits: Seq[Seq[util.JGitUtil.CommitInfo]],
diffs: Seq[util.JGitUtil.DiffInfo],
members: List[(String, String)],
comments: List[model.Comment],
originId: String,
forkedId: String,
sourceId: String,
@@ -81,8 +82,10 @@
</tr>
</table>
} else {
@pulls.html.commits(commits, repository)
@helper.html.diff(diffs, repository, Some(commitId), Some(sourceId), true)
@pulls.html.commits(commits, Some(comments), repository)
@helper.html.diff(diffs, repository, Some(commitId), Some(sourceId), true, false, 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,
pullreq: model.PullRequest,
comments: List[model.IssueComment],
comments: List[model.Comment],
issueLabels: List[model.Label],
collaborators: List[String],
milestones: List[(model.Milestone, Int, Int)],
@@ -8,11 +8,17 @@
hasWritePermission: Boolean,
repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context)
@import context._
@import model.IssueComment
@import view.helpers._
<div class="row-fluid">
<div class="span10">
@issues.html.commentlist(issue, comments, hasWritePermission, repository, Some(pullreq))
@defining(comments.exists(_.action == "merge")){ merged =>
<div id="comment-list">
@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){
<div class="box issue-comment-box" style="background-color: #d8f5cd;">
<div class="box-content"class="issue-content" style="border: 1px solid #95c97e; padding: 10px;">

View File

@@ -1,6 +1,6 @@
@(issue: model.Issue,
pullreq: model.PullRequest,
comments: List[model.IssueComment],
comments: List[model.Comment],
issueLabels: List[model.Label],
collaborators: List[String],
milestones: List[(model.Milestone, Int, Int)],
@@ -36,7 +36,10 @@
</h1>
</div>
@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="muted">
@user(comment.commentedUserName, styleClass="username strong") merged @commits.size @plural(commits.size, "commit")
@@ -59,7 +62,10 @@
}
<br/><br/>
<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="#files">Files Changed <span class="badge">@diffs.size</span></a></li>
</ul>
@@ -68,10 +74,10 @@
@pulls.html.conversation(issue, pullreq, comments, issueLabels, collaborators, milestones, labels, hasWritePermission, repository)
</div>
<div class="tab-pane" id="commits">
@pulls.html.commits(dayByDayCommits, repository)
@pulls.html.commits(dayByDayCommits, Some(comments), repository)
</div>
<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, true, hasWritePermission, true)
</div>
</div>
}

View File

@@ -0,0 +1,61 @@
@(commitId: String,
fileName: Option[String] = None,
oldLineNumber: Option[Int] = None,
newLineNumber: Option[Int] = None,
pullRequest: Boolean,
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>
}
<input type="hidden" name="pullRequest" value="@pullRequest">
@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,
branches: List[String],
tags: List[String],
comments: List[model.Comment],
repository: service.RepositoryService.RepositoryInfo,
diffs: Seq[util.JGitUtil.DiffInfo],
oldCommitId: Option[String])(implicit context: app.Context)
oldCommitId: Option[String],
hasWritePermission: Boolean)(implicit context: app.Context)
@import context._
@import view.helpers._
@import util.Implicits._
@@ -81,7 +83,14 @@
</td>
</tr>
</table>
@helper.html.diff(diffs, repository, Some(commit.id), oldCommitId, true)
@helper.html.diff(diffs, repository, Some(commit.id), oldCommitId, true, false, 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, pullRequest = false, hasWritePermission = hasWritePermission, repository = repository)
}
}
<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>
<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

@@ -127,7 +127,8 @@ $(function(){
$.post('@url(repository)/_preview', {
content : editor.getValue(),
enableWikiLink : false,
enableRefsLink : false
enableRefsLink : false,
enableTaskList : false
}, function(data){
$('#preview').empty().append(
$('<div class="markdown-body" style="padding-left: 16px; padding-right: 16px;">').html(data));

View File

@@ -7,21 +7,37 @@
@if(!hasWritePermission){
<h3>This is an empty repository</h3>
} else {
<h3><strong>Quick setup</strong> — if you've done this kind of thing before</h3>
<div class="empty-repo-options">
via <a href="@repository.httpUrl" class="git-protocol-selector">HTTP</a>
@if(settings.ssh && loginAccount.isDefined){
or
<a href="@repository.sshUrl(settings.sshPort.getOrElse(service.SystemSettingsService.DefaultSshPort), loginAccount.get.userName)" class="git-protocol-selector">SSH</a>
}
</div>
<h3 style="margin-top: 30px;">Create a new repository on the command line</h3>
<pre>
touch README.md
git init
git add README.md
git commit -m "first commit"
git remote add origin @repository.httpUrl
git remote add origin <span class="live-clone-url">@repository.httpUrl</span>
git push -u origin master
</pre>
<h3 style="margin-top: 30px;">Push an existing repository from the command line</h3>
<pre>
git remote add origin @repository.httpUrl
git remote add origin <span class="live-clone-url">@repository.httpUrl</span>
git push -u origin master
</pre>
<script>
$(function(){
$('.git-protocol-selector').click(function(e){
e.preventDefault();
$('.live-clone-url').text($(e.target).attr('href'));
});
});
</script>
}
}
}

View File

@@ -16,7 +16,7 @@
@issues.drop((page - 1) * IssueLimit).take(IssueLimit).map { issue =>
<div class="block">
<div class="pull-right muted">#@issue.issueId</div>
<h4 style="margin-top: 0px;"><a href="@url(repository)/issues/@issue.issueId">@issue.title</a></h4>
<h4 style="margin-top: 0px;"><a href="@url(repository)/@if(issue.isPullRequest){pull} else {issues}/@issue.issueId">@issue.title</a></h4>
@if(issue.highlightText.nonEmpty){
<pre>@Html(issue.highlightText)</pre>
}

View File

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

View File

@@ -226,6 +226,10 @@ div.box-header-small {
text-shadow: 0 1px 0 #fff
}
.inline-comment div.box-header-small {
background: #f2f8fa;
}
div.box-content {
background-color: white;
border: 1px solid #d8d8d8;
@@ -860,9 +864,10 @@ div.issue-participants {
margin-left: 50px;
}
div.issue-comment-box {
div.issue-comment-box, div.commit-comment-box {
margin-bottom: 15px;
margin-left: 50px;
max-width: 820px;
}
div.issue-comment-action {
@@ -959,13 +964,45 @@ table.diff thead {
}
table.inlinediff td.insert, table.inlinediff td.equal, table.inlinediff td.delete {
width: 100%;
width: 900px;
}
td.insert, td.equal, td.delete, td.empty {
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 */
/****************************************************************************/

View File

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

View File

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

View File

@@ -78,6 +78,16 @@ diffview = {
e.appendChild(document.createTextNode(text));
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 node = document.createElement("tr");
@@ -119,8 +129,8 @@ diffview = {
}
function addCellsInline (row, tidx, tidx2, textLines, change) {
row.appendChild(telt("th", tidx == null ? "" : (tidx + 1).toString()));
row.appendChild(telt("th", tidx2 == null ? "" : (tidx2 + 1).toString()));
row.appendChild(ctelt("th", "oldline", tidx == null ? "" : (tidx + 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")));
}