diff --git a/src/main/resources/noimage.png b/src/main/resources/noimage.png
index c48285f..39d1ab4 100644
--- a/src/main/resources/noimage.png
+++ b/src/main/resources/noimage.png
Binary files differ
diff --git a/src/main/scala/app/AccountController.scala b/src/main/scala/app/AccountController.scala
index d8e6362..68b2d53 100644
--- a/src/main/scala/app/AccountController.scala
+++ b/src/main/scala/app/AccountController.scala
@@ -58,7 +58,10 @@
getAccountByUserName(userName).flatMap(_.image).map { image =>
contentType = FileUtil.getMimeType(image)
new java.io.File(getUserUploadDir(userName), image)
- } getOrElse NotFound
+ } getOrElse {
+ contentType = "image/png"
+ Thread.currentThread.getContextClassLoader.getResourceAsStream("noimage.png")
+ }
}
get("/:userName/_edit")(oneselfOnly {
@@ -76,7 +79,7 @@
updateImage(userName, form.fileId, form.clearImage)
flash += "info" -> "Account information has been updated."
- redirect("/%s/_edit".format(userName))
+ redirect(s"/${userName}/_edit")
} getOrElse NotFound
})
diff --git a/src/main/scala/app/CreateRepositoryController.scala b/src/main/scala/app/CreateRepositoryController.scala
index ee620e8..d89d306 100644
--- a/src/main/scala/app/CreateRepositoryController.scala
+++ b/src/main/scala/app/CreateRepositoryController.scala
@@ -69,9 +69,13 @@
// Create README.md
FileUtils.writeStringToFile(new File(tmpdir, "README.md"),
if(form.description.nonEmpty){
- form.name + "\n===============\n\n" + form.description.get
+ form.name + "\n" +
+ "===============\n" +
+ "\n" +
+ form.description.get
} else {
- form.name + "\n===============\n"
+ form.name + "\n" +
+ "===============\n"
}, "UTF-8")
val git = Git.open(tmpdir)
@@ -91,7 +95,7 @@
recordCreateRepositoryActivity(loginUserName, form.name, loginUserName)
// redirect to the repository
- redirect("/%s/%s".format(loginUserName, form.name))
+ redirect(s"/${loginUserName}/${form.name}")
})
post("/:owner/:repository/_fork")(referrersOnly { repository =>
diff --git a/src/main/scala/app/IndexController.scala b/src/main/scala/app/IndexController.scala
index 7eff0cd..c16873b 100644
--- a/src/main/scala/app/IndexController.scala
+++ b/src/main/scala/app/IndexController.scala
@@ -9,10 +9,13 @@
with SystemSettingsService with ActivityService =>
get("/"){
+ val loginAccount = context.loginAccount
+
html.index(getRecentActivities(),
- getAccessibleRepositories(context.loginAccount, baseUrl),
+ getAccessibleRepositories(loginAccount, baseUrl),
loadSystemSettings(),
- context.loginAccount.map{ account => getRepositoryNamesOfUser(account.userName) }.getOrElse(Nil))
+ loginAccount.map{ account => getRepositoryNamesOfUser(account.userName) }.getOrElse(Nil)
+ )
}
}
\ No newline at end of file
diff --git a/src/main/scala/app/IssuesController.scala b/src/main/scala/app/IssuesController.scala
index 72b3d70..307610d 100644
--- a/src/main/scala/app/IssuesController.scala
+++ b/src/main/scala/app/IssuesController.scala
@@ -17,10 +17,9 @@
case class IssueCreateForm(title: String, content: Option[String],
assignedUserName: Option[String], milestoneId: Option[Int], labelNames: Option[String])
-
case class IssueEditForm(title: String, content: Option[String])
-
case class CommentForm(issueId: Int, content: String)
+ case class IssueStateForm(issueId: Int, content: Option[String])
val issueCreateForm = mapping(
"title" -> trim(label("Title", text(required))),
@@ -40,6 +39,11 @@
"content" -> trim(label("Comment", text(required)))
)(CommentForm.apply)
+ val issueStateForm = mapping(
+ "issueId" -> label("Issue Id", number()),
+ "content" -> trim(optional(text()))
+ )(IssueStateForm.apply)
+
get("/:owner/:repository/issues")(referrersOnly {
searchIssues("all", _)
})
@@ -124,29 +128,11 @@
})
post("/:owner/:repository/issue_comments/new", commentForm)(readableUsersOnly { (form, repository) =>
- val owner = repository.owner
- val name = repository.name
- val userName = context.loginAccount.get.userName
+ handleComment(form.issueId, Some(form.content), repository)
+ })
- getIssue(owner, name, form.issueId.toString).map { issue =>
- val action = if(isEditable(owner, name, issue.openedUserName)){
- params.get("action") filter { action =>
- updateClosed(owner, name, form.issueId, if(action == "close") true else false) > 0
- }
- } else None
-
- val commentId = createComment(owner, name, userName, form.issueId, form.content, action)
-
- // record activity
- recordCommentIssueActivity(owner, name, userName, issue.issueId, form.content)
- action match {
- case Some("reopen") => recordReopenIssueActivity(owner, name, userName, issue.issueId, issue.title)
- case Some("close") => recordCloseIssueActivity(owner, name, userName, issue.issueId, issue.title)
- case _ =>
- }
-
- redirect("/%s/%s/issues/%d#comment-%d".format(owner, name, form.issueId, commentId))
- }
+ post("/:owner/:repository/issue_comments/state", issueStateForm)(readableUsersOnly { (form, repository) =>
+ handleComment(form.issueId, form.content, repository)
})
ajaxPost("/:owner/:repository/issue_comments/edit/:id", commentForm)(readableUsersOnly { (form, repository) =>
@@ -172,7 +158,7 @@
org.json4s.jackson.Serialization.write(
Map("title" -> x.title,
"content" -> view.Markdown.toHtml(x.content getOrElse "No description given.",
- repository, false, true, true)
+ repository, false, true)
))
}
} else Unauthorized
@@ -189,7 +175,7 @@
contentType = formats("json")
org.json4s.jackson.Serialization.write(
Map("content" -> view.Markdown.toHtml(x.content,
- repository, false, true, true)
+ repository, false, true)
))
}
} else Unauthorized
@@ -222,9 +208,85 @@
Ok("updated")
})
+ post("/:owner/:repository/issues/batchedit/state")(collaboratorsOnly { repository =>
+ val owner = repository.owner
+ val name = repository.name
+ val userName = context.loginAccount.get.userName
+
+ params.get("value") collect {
+ case s if s == "close" => (s.capitalize, Some(s), true)
+ case s if s == "reopen" => (s.capitalize, Some(s), false)
+ } map { case (content, action, closed) =>
+ params("checked").split(',') foreach { issueId =>
+ createComment(owner, name, userName, issueId.toInt, content, action)
+ updateClosed(owner, name, issueId.toInt, closed)
+ }
+ redirect("/%s/%s/issues".format(owner, name))
+ } getOrElse NotFound
+ })
+
+ post("/:owner/:repository/issues/batchedit/label")(collaboratorsOnly { repository =>
+ val owner = repository.owner
+ val name = repository.name
+
+ params.get("value").map(_.toInt) map { labelId =>
+ params("checked").split(',') foreach { issueId =>
+ getIssueLabel(owner, name, issueId.toInt, labelId) getOrElse {
+ registerIssueLabel(owner, name, issueId.toInt, labelId)
+ }
+ }
+ redirect("/%s/%s/issues".format(owner, name))
+ } getOrElse NotFound
+ })
+
+ post("/:owner/:repository/issues/batchedit/assign")(collaboratorsOnly { repository =>
+ params("checked").split(',') foreach { issueId =>
+ updateAssignedUserName(repository.owner, repository.name, issueId.toInt,
+ params.get("value") filter (_.trim != ""))
+ }
+ redirect("/%s/%s/issues".format(repository.owner, repository.name))
+ })
+
+ post("/:owner/:repository/issues/batchedit/milestone")(collaboratorsOnly { repository =>
+ params("checked").split(',') foreach { issueId =>
+ updateMilestoneId(repository.owner, repository.name, issueId.toInt,
+ params.get("value") collect { case x if x.trim != "" => x.toInt })
+ }
+ redirect("/%s/%s/issues".format(repository.owner, repository.name))
+ })
+
private def isEditable(owner: String, repository: String, author: String)(implicit context: app.Context): Boolean =
hasWritePermission(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName
+ private def handleComment(issueId: Int, content: Option[String], repository: RepositoryService.RepositoryInfo) = {
+ val owner = repository.owner
+ val name = repository.name
+ val userName = context.loginAccount.get.userName
+
+ getIssue(owner, name, issueId.toString) map { issue =>
+ val (action, recordActivity) =
+ params.get("action")
+ .filter(_ => isEditable(owner, name, issue.openedUserName))
+ .collect {
+ case s if s == "close" => true -> (Some(s) -> Some(recordCloseIssueActivity _))
+ case s if s == "reopen" => false -> (Some(s) -> Some(recordReopenIssueActivity _))
+ }
+ .map { case (closed, t) =>
+ updateClosed(owner, name, issueId, closed)
+ t
+ }
+ .getOrElse(None -> None)
+
+ val commentId = createComment(owner, name, userName, issueId, content.getOrElse(action.get.capitalize), action)
+
+ // record activity
+ content foreach ( recordCommentIssueActivity(owner, name, userName, issueId, _) )
+ recordActivity foreach ( _ (owner, name, userName, issueId, issue.title) )
+
+ redirect("/%s/%s/issues/%d#comment-%d".format(owner, name, issueId, commentId))
+ } getOrElse NotFound
+ }
+
private def searchIssues(filter: String, repository: RepositoryService.RepositoryInfo) = {
val owner = repository.owner
val repoName = repository.name
@@ -248,8 +310,9 @@
issues.html.list(
searchIssue(owner, repoName, condition, filter, userName, (page - 1) * IssueLimit, IssueLimit),
page,
- getLabels(owner, repoName),
+ (getCollaborators(owner, repoName) :+ owner).sorted,
getMilestones(owner, repoName).filter(_.closedDate.isEmpty),
+ getLabels(owner, repoName),
countIssue(owner, repoName, condition.copy(state = "open"), filter, userName),
countIssue(owner, repoName, condition.copy(state = "closed"), filter, userName),
countIssue(owner, repoName, condition, "all", None),
diff --git a/src/main/scala/app/LabelsController.scala b/src/main/scala/app/LabelsController.scala
index e01c63e..e666d67 100644
--- a/src/main/scala/app/LabelsController.scala
+++ b/src/main/scala/app/LabelsController.scala
@@ -24,7 +24,7 @@
post("/:owner/:repository/issues/label/new", newForm)(collaboratorsOnly { (form, repository) =>
createLabel(repository.owner, repository.name, form.labelName, form.color.substring(1))
- redirect("/%s/%s/issues".format(repository.owner, repository.name))
+ redirect(s"/${repository.owner}/${repository.name}/issues")
})
ajaxGet("/:owner/:repository/issues/label/edit")(collaboratorsOnly { repository =>
@@ -53,9 +53,9 @@
private def labelName: Constraint = new Constraint(){
def validate(name: String, value: String): Option[String] =
if(!value.matches("^[^,]+$")){
- Some("%s contains invalid character.".format(name))
+ Some(s"${name} contains invalid character.")
} else if(value.startsWith("_") || value.startsWith("-")){
- Some("%s starts with invalid character.".format(name))
+ Some(s"${name} starts with invalid character.")
} else {
None
}
diff --git a/src/main/scala/app/MilestonesController.scala b/src/main/scala/app/MilestonesController.scala
index 480b8bc..55c60d0 100644
--- a/src/main/scala/app/MilestonesController.scala
+++ b/src/main/scala/app/MilestonesController.scala
@@ -35,7 +35,7 @@
post("/:owner/:repository/issues/milestones/new", milestoneForm)(collaboratorsOnly { (form, repository) =>
createMilestone(repository.owner, repository.name, form.title, form.description, form.dueDate)
- redirect("/%s/%s/issues/milestones".format(repository.owner, repository.name))
+ redirect(s"/${repository.owner}/${repository.name}/issues/milestones")
})
get("/:owner/:repository/issues/milestones/:milestoneId/edit")(collaboratorsOnly { repository =>
@@ -45,28 +45,28 @@
post("/:owner/:repository/issues/milestones/:milestoneId/edit", milestoneForm)(collaboratorsOnly { (form, repository) =>
getMilestone(repository.owner, repository.name, params("milestoneId").toInt).map { milestone =>
updateMilestone(milestone.copy(title = form.title, description = form.description, dueDate = form.dueDate))
- redirect("/%s/%s/issues/milestones".format(repository.owner, repository.name))
+ redirect(s"/${repository.owner}/${repository.name}/issues/milestones")
} getOrElse NotFound
})
get("/:owner/:repository/issues/milestones/:milestoneId/close")(collaboratorsOnly { repository =>
getMilestone(repository.owner, repository.name, params("milestoneId").toInt).map { milestone =>
closeMilestone(milestone)
- redirect("/%s/%s/issues/milestones".format(repository.owner, repository.name))
+ redirect(s"/${repository.owner}/${repository.name}/issues/milestones")
} getOrElse NotFound
})
get("/:owner/:repository/issues/milestones/:milestoneId/open")(collaboratorsOnly { repository =>
getMilestone(repository.owner, repository.name, params("milestoneId").toInt).map { milestone =>
openMilestone(milestone)
- redirect("/%s/%s/issues/milestones".format(repository.owner, repository.name))
+ redirect(s"/${repository.owner}/${repository.name}/issues/milestones")
} getOrElse NotFound
})
get("/:owner/:repository/issues/milestones/:milestoneId/delete")(collaboratorsOnly { repository =>
getMilestone(repository.owner, repository.name, params("milestoneId").toInt).map { milestone =>
deleteMilestone(repository.owner, repository.name, milestone.milestoneId)
- redirect("/%s/%s/issues/milestones".format(repository.owner, repository.name))
+ redirect(s"/${repository.owner}/${repository.name}/issues/milestones")
} getOrElse NotFound
})
diff --git a/src/main/scala/app/RepositorySettingsController.scala b/src/main/scala/app/RepositorySettingsController.scala
index a5c79b4..08f6474 100644
--- a/src/main/scala/app/RepositorySettingsController.scala
+++ b/src/main/scala/app/RepositorySettingsController.scala
@@ -31,7 +31,7 @@
* Redirect to the Options page.
*/
get("/:owner/:repository/settings")(ownerOnly { repository =>
- redirect("/%s/%s/settings/options".format(repository.owner, repository.name))
+ redirect(s"/${repository.owner}/${repository.name}/settings/options")
})
/**
@@ -47,7 +47,7 @@
post("/:owner/:repository/settings/options", optionsForm)(ownerOnly { (form, repository) =>
saveRepositoryOptions(repository.owner, repository.name, form.description, form.defaultBranch, form.isPrivate)
flash += "info" -> "Repository settings has been updated."
- redirect("/%s/%s/settings/options".format(repository.owner, repository.name))
+ redirect(s"/${repository.owner}/${repository.name}/settings/options")
})
/**
@@ -70,7 +70,7 @@
*/
post("/:owner/:repository/settings/collaborators/add", collaboratorForm)(ownerOnly { (form, repository) =>
addCollaborator(repository.owner, repository.name, form.userName)
- redirect("/%s/%s/settings/collaborators".format(repository.owner, repository.name))
+ redirect(s"/${repository.owner}/${repository.name}/settings/collaborators")
})
/**
@@ -78,7 +78,7 @@
*/
get("/:owner/:repository/settings/collaborators/remove")(ownerOnly { repository =>
removeCollaborator(repository.owner, repository.name, params("name"))
- redirect("/%s/%s/settings/collaborators".format(repository.owner, repository.name))
+ redirect(s"/${repository.owner}/${repository.name}/settings/collaborators")
})
/**
@@ -98,7 +98,7 @@
FileUtils.deleteDirectory(getWikiRepositoryDir(repository.owner, repository.name))
FileUtils.deleteDirectory(getTemporaryDir(repository.owner, repository.name))
- redirect("/%s".format(repository.owner))
+ redirect(s"/${repository.owner}")
})
/**
diff --git a/src/main/scala/app/RepositoryViewerController.scala b/src/main/scala/app/RepositoryViewerController.scala
index 4ba43cb..43cb126 100644
--- a/src/main/scala/app/RepositoryViewerController.scala
+++ b/src/main/scala/app/RepositoryViewerController.scala
@@ -27,8 +27,7 @@
contentType = "text/html"
view.helpers.markdown(params("content"), repository,
params("enableWikiLink").toBoolean,
- params("enableCommitLink").toBoolean,
- params("enableIssueLink").toBoolean)
+ params("enableRefsLink").toBoolean)
})
/**
@@ -58,13 +57,14 @@
get("/:owner/:repository/commits/:branch")(referrersOnly { repository =>
val branchName = params("branch")
val page = params.getOrElse("page", "1").toInt
-
JGitUtil.withGit(getRepositoryDir(repository.owner, repository.name)){ git =>
- val (logs, hasNext) = JGitUtil.getCommitLog(git, branchName, page, 30)
-
- repo.html.commits(Nil, branchName, repository, logs.splitWith{ (commit1, commit2) =>
- view.helpers.date(commit1.time) == view.helpers.date(commit2.time)
- }, page, hasNext)
+ JGitUtil.getCommitLog(git, branchName, page, 30) match {
+ case Right((logs, hasNext)) =>
+ repo.html.commits(Nil, branchName, repository, logs.splitWith{ (commit1, commit2) =>
+ view.helpers.date(commit1.time) == view.helpers.date(commit2.time)
+ }, page, hasNext)
+ case Left(_) => NotFound
+ }
}
})
@@ -77,12 +77,14 @@
val page = params.getOrElse("page", "1").toInt
JGitUtil.withGit(getRepositoryDir(repository.owner, repository.name)){ git =>
- val (logs, hasNext) = JGitUtil.getCommitLog(git, branchName, page, 30, path)
-
- repo.html.commits(path.split("/").toList, branchName, repository,
- logs.splitWith{ (commit1, commit2) =>
- view.helpers.date(commit1.time) == view.helpers.date(commit2.time)
- }, page, hasNext)
+ JGitUtil.getCommitLog(git, branchName, page, 30, path) match {
+ case Right((logs, hasNext)) =>
+ repo.html.commits(path.split("/").toList, branchName, repository,
+ logs.splitWith{ (commit1, commit2) =>
+ view.helpers.date(commit1.time) == view.helpers.date(commit2.time)
+ }, page, hasNext)
+ case Left(_) => NotFound
+ }
}
})
@@ -214,27 +216,23 @@
repo.html.guide(repository)
} else {
JGitUtil.withGit(getRepositoryDir(repository.owner, repository.name)){ git =>
+ val revisions = Seq(if(revstr.isEmpty) repository.repository.defaultBranch else revstr, repository.branchList.head)
// get specified commit
- val (revCommit, revision) = try {
- val revision = if(revstr.isEmpty) repository.repository.defaultBranch else revstr
- (JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(revision)), revision)
- } catch {
- case e: NullPointerException => {
- val revision = repository.branchList.head
- (JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(revision)), revision)
- }
- }
- // get files
- val files = JGitUtil.getFileList(git, revision, path)
- // process README.md
- val readme = files.find(_.name == "README.md").map { file =>
- new String(JGitUtil.getContent(Git.open(getRepositoryDir(repository.owner, repository.name)), file.id, true).get, "UTF-8")
- }
+ revisions.map { rev => (git.getRepository.resolve(rev), rev)}.find(_._1 != null).map { case (objectId, revision) =>
+ val revCommit = JGitUtil.getRevCommitFromId(git, objectId)
- repo.html.files(revision, repository,
- if(path == ".") Nil else path.split("/").toList, // current path
- new JGitUtil.CommitInfo(revCommit), // latest commit
- files, readme)
+ // get files
+ val files = JGitUtil.getFileList(git, revision, path)
+ // process README.md
+ val readme = files.find(_.name == "README.md").map { file =>
+ new String(JGitUtil.getContent(Git.open(getRepositoryDir(repository.owner, repository.name)), file.id, true).get, "UTF-8")
+ }
+
+ repo.html.files(revision, repository,
+ if(path == ".") Nil else path.split("/").toList, // current path
+ new JGitUtil.CommitInfo(revCommit), // latest commit
+ files, readme)
+ } getOrElse NotFound
}
}
}
diff --git a/src/main/scala/app/WikiController.scala b/src/main/scala/app/WikiController.scala
index 405733d..932f9e2 100644
--- a/src/main/scala/app/WikiController.scala
+++ b/src/main/scala/app/WikiController.scala
@@ -1,7 +1,7 @@
package app
import service._
-import util.{CollaboratorsAuthenticator, ReferrerAuthenticator, JGitUtil}
+import util.{CollaboratorsAuthenticator, ReferrerAuthenticator, JGitUtil, StringUtil}
import util.Directory._
import jp.sf.amateras.scalatra.forms._
@@ -16,14 +16,14 @@
case class WikiPageEditForm(pageName: String, content: String, message: Option[String], currentPageName: String)
val newForm = mapping(
- "pageName" -> trim(label("Page name" , text(required, maxlength(40), identifier, unique))),
+ "pageName" -> trim(label("Page name" , text(required, maxlength(40), pagename, unique))),
"content" -> trim(label("Content" , text(required))),
"message" -> trim(label("Message" , optional(text()))),
"currentPageName" -> trim(label("Current page name" , text()))
)(WikiPageEditForm.apply)
val editForm = mapping(
- "pageName" -> trim(label("Page name" , text(required, maxlength(40), identifier))),
+ "pageName" -> trim(label("Page name" , text(required, maxlength(40), pagename))),
"content" -> trim(label("Content" , text(required))),
"message" -> trim(label("Message" , optional(text()))),
"currentPageName" -> trim(label("Current page name" , text(required)))
@@ -32,27 +32,30 @@
get("/:owner/:repository/wiki")(referrersOnly { repository =>
getWikiPage(repository.owner, repository.name, "Home").map { page =>
wiki.html.page("Home", page, repository, hasWritePermission(repository.owner, repository.name, context.loginAccount))
- } getOrElse redirect("/%s/%s/wiki/Home/_edit".format(repository.owner, repository.name))
+ } getOrElse redirect(s"/${repository.owner}/${repository.name}/wiki/Home/_edit")
})
get("/:owner/:repository/wiki/:page")(referrersOnly { repository =>
- val pageName = params("page")
+ val pageName = StringUtil.urlDecode(params("page"))
getWikiPage(repository.owner, repository.name, pageName).map { page =>
wiki.html.page(pageName, page, repository, hasWritePermission(repository.owner, repository.name, context.loginAccount))
- } getOrElse redirect("/%s/%s/wiki/%s/_edit".format(repository.owner, repository.name, pageName)) // TODO URLEncode
+ } getOrElse redirect(s"/${repository.owner}/${repository.name}/wiki/${pageName}/_edit") // TODO URLEncode
})
get("/:owner/:repository/wiki/:page/_history")(referrersOnly { repository =>
- val pageName = params("page")
+ val pageName = StringUtil.urlDecode(params("page"))
JGitUtil.withGit(getWikiRepositoryDir(repository.owner, repository.name)){ git =>
- wiki.html.history(Some(pageName), JGitUtil.getCommitLog(git, "master", path = pageName + ".md")._1, repository)
+ JGitUtil.getCommitLog(git, "master", path = pageName + ".md") match {
+ case Right((logs, hasNext)) => wiki.html.history(Some(pageName), logs, repository)
+ case Left(_) => NotFound
+ }
}
})
get("/:owner/:repository/wiki/:page/_compare/:commitId")(referrersOnly { repository =>
- val pageName = params("page")
+ val pageName = StringUtil.urlDecode(params("page"))
val commitId = params("commitId").split("\\.\\.\\.")
JGitUtil.withGit(getWikiRepositoryDir(repository.owner, repository.name)){ git =>
@@ -69,7 +72,7 @@
})
get("/:owner/:repository/wiki/:page/_edit")(collaboratorsOnly { repository =>
- val pageName = params("page")
+ val pageName = StringUtil.urlDecode(params("page"))
wiki.html.edit(pageName, getWikiPage(repository.owner, repository.name, pageName), repository)
})
@@ -82,7 +85,7 @@
updateLastActivityDate(repository.owner, repository.name)
recordEditWikiPageActivity(repository.owner, repository.name, loginAccount.userName, form.pageName)
- redirect("/%s/%s/wiki/%s".format(repository.owner, repository.name, form.pageName))
+ redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(form.pageName)}")
})
get("/:owner/:repository/wiki/_new")(collaboratorsOnly {
@@ -98,16 +101,16 @@
updateLastActivityDate(repository.owner, repository.name)
recordCreateWikiPageActivity(repository.owner, repository.name, loginAccount.userName, form.pageName)
- redirect("/%s/%s/wiki/%s".format(repository.owner, repository.name, form.pageName))
+ redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(form.pageName)}")
})
get("/:owner/:repository/wiki/:page/_delete")(collaboratorsOnly { repository =>
- val pageName = params("page")
+ val pageName = StringUtil.urlDecode(params("page"))
- deleteWikiPage(repository.owner, repository.name, pageName, context.loginAccount.get.userName, "Delete %s".format(pageName))
+ deleteWikiPage(repository.owner, repository.name, pageName, context.loginAccount.get.userName, s"Delete ${pageName}")
updateLastActivityDate(repository.owner, repository.name)
- redirect("/%s/%s/wiki".format(repository.owner, repository.name))
+ redirect(s"/${repository.owner}/${repository.name}/wiki")
})
get("/:owner/:repository/wiki/_pages")(referrersOnly { repository =>
@@ -117,7 +120,10 @@
get("/:owner/:repository/wiki/_history")(referrersOnly { repository =>
JGitUtil.withGit(getWikiRepositoryDir(repository.owner, repository.name)){ git =>
- wiki.html.history(None, JGitUtil.getCommitLog(git, "master")._1, repository)
+ JGitUtil.getCommitLog(git, "master") match {
+ case Right((logs, hasNext)) => wiki.html.history(None, logs, repository)
+ case Left(_) => NotFound
+ }
}
})
@@ -133,4 +139,16 @@
getWikiPageList(params("owner"), params("repository")).find(_ == value).map(_ => "Page already exists.")
}
+ private def pagename: Constraint = new Constraint(){
+ def validate(name: String, value: String): Option[String] =
+ if(value.exists("\\/:*?\"<>|".contains(_))){
+ Some(s"${name} contains invalid character.")
+ } else if(value.startsWith("_") || value.startsWith("-")){
+ Some(s"${name} starts with invalid character.")
+ } else {
+ None
+ }
+ }
+
+
}
\ No newline at end of file
diff --git a/src/main/scala/service/ActivityService.scala b/src/main/scala/service/ActivityService.scala
index 2ead13c..4621464 100644
--- a/src/main/scala/service/ActivityService.scala
+++ b/src/main/scala/service/ActivityService.scala
@@ -55,7 +55,7 @@
def recordReopenIssueActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, title: String): Unit =
Activities.autoInc insert(userName, repositoryName, activityUserName,
"reopen_issue",
- s"[user:${activityUserName}] closed reopened [issue:${userName}/${repositoryName}#${issueId}]",
+ s"[user:${activityUserName}] reopened issue [issue:${userName}/${repositoryName}#${issueId}]",
Some(title),
currentDate)
diff --git a/src/main/scala/service/IssuesService.scala b/src/main/scala/service/IssuesService.scala
index 9641aa4..17f3c69 100644
--- a/src/main/scala/service/IssuesService.scala
+++ b/src/main/scala/service/IssuesService.scala
@@ -6,6 +6,7 @@
import Q.interpolation
import model._
+import util.StringUtil._
import util.Implicits._
trait IssuesService {
@@ -35,6 +36,9 @@
.map ( _._2 )
.list
+ def getIssueLabel(owner: String, repository: String, issueId: Int, labelId: Int) =
+ Query(IssueLabels) filter (_.byPrimaryKey(owner, repository, issueId, labelId)) firstOption
+
/**
* Returns the count of the search result against issues.
*
@@ -233,7 +237,6 @@
}
object IssuesService {
- import java.net.URLEncoder
import javax.servlet.http.HttpServletRequest
val IssueLimit = 30
@@ -245,8 +248,6 @@
sort: String = "created",
direction: String = "desc"){
- import IssueSearchCondition._
-
def toURL: String =
"?" + List(
if(labels.isEmpty) None else Some("labels=" + urlEncode(labels.mkString(" "))),
@@ -262,8 +263,6 @@
object IssueSearchCondition {
- private def urlEncode(value: String): String = URLEncoder.encode(value, "UTF-8")
-
private def param(request: HttpServletRequest, name: String, allow: Seq[String] = Nil): Option[String] = {
val value = request.getParameter(name)
if(value == null || value.isEmpty || (allow.nonEmpty && !allow.contains(value))) None else Some(value)
diff --git a/src/main/scala/service/RequestCache.scala b/src/main/scala/service/RequestCache.scala
new file mode 100644
index 0000000..758c373
--- /dev/null
+++ b/src/main/scala/service/RequestCache.scala
@@ -0,0 +1,26 @@
+package service
+
+import model._
+
+/**
+ * This service is used for a view helper mainly.
+ *
+ * It may be called many times in one request, so each method stores
+ * its result into the cache which available during a request.
+ */
+trait RequestCache {
+
+ def getIssue(userName: String, repositoryName: String, issueId: String)(implicit context: app.Context): Option[Issue] = {
+ context.cache(s"issue.${userName}/${repositoryName}#${issueId}"){
+ new IssuesService {}.getIssue(userName, repositoryName, issueId)
+ }
+ }
+
+ def getAccountByUserName(userName: String)(implicit context: app.Context): Option[Account] = {
+ context.cache(s"account.${userName}"){
+ new AccountService {}.getAccountByUserName(userName)
+ }
+ }
+
+}
+
diff --git a/src/main/scala/servlet/AutoUpdateListener.scala b/src/main/scala/servlet/AutoUpdateListener.scala
index 266e617..4614230 100644
--- a/src/main/scala/servlet/AutoUpdateListener.scala
+++ b/src/main/scala/servlet/AutoUpdateListener.scala
@@ -25,7 +25,7 @@
* If corresponding SQL file does not exist, this method do nothing.
*/
def update(conn: Connection): Unit = {
- val sqlPath = "update/%d_%d.sql".format(majorVersion, minorVersion)
+ val sqlPath = s"update/${majorVersion}_${minorVersion}.sql"
val in = Thread.currentThread.getContextClassLoader.getResourceAsStream(sqlPath)
if(in != null){
val sql = IOUtils.toString(in, "UTF-8")
@@ -42,14 +42,30 @@
/**
* MAJOR.MINOR
*/
- val versionString = "%d.%d".format(majorVersion, minorVersion)
+ val versionString = s"${majorVersion}.${minorVersion}"
}
-
+
/**
* The history of versions. A head of this sequence is the current BitBucket version.
*/
val versions = Seq(
- Version(1, 3),
+ new Version(1, 3){
+ override def update(conn: Connection): Unit = {
+ super.update(conn)
+ // Fix wiki repository configuration
+ val rs = conn.createStatement.executeQuery("SELECT USER_NAME, REPOSITORY_NAME FROM REPOSITORY")
+ while(rs.next){
+ val wikidir = Directory.getWikiRepositoryDir(rs.getString("USER_NAME"), rs.getString("REPOSITORY_NAME"))
+ val repository = org.eclipse.jgit.api.Git.open(wikidir).getRepository
+ val config = repository.getConfig
+ if(!config.getBoolean("http", "receivepack", false)){
+ config.setBoolean("http", null, "receivepack", true)
+ config.save
+ }
+ repository.close
+ }
+ }
+ },
Version(1, 2),
Version(1, 1),
Version(1, 0)
diff --git a/src/main/scala/servlet/TransactionFilter.scala b/src/main/scala/servlet/TransactionFilter.scala
index df73db5..5a26734 100644
--- a/src/main/scala/servlet/TransactionFilter.scala
+++ b/src/main/scala/servlet/TransactionFilter.scala
@@ -6,7 +6,7 @@
import scala.slick.session.Database
/**
- * Controls the transaction with the open session in view pattern.
+ * Controls the transaction with the open session in view pattern.
*/
class TransactionFilter extends Filter {
@@ -21,7 +21,6 @@
// assets don't need transaction
chain.doFilter(req, res)
} else {
- // TODO begin transaction!
val context = req.getServletContext
Database.forURL(context.getInitParameter("db.url"),
context.getInitParameter("db.user"),
diff --git a/src/main/scala/util/Directory.scala b/src/main/scala/util/Directory.scala
index b378a99..f2b7f4c 100644
--- a/src/main/scala/util/Directory.scala
+++ b/src/main/scala/util/Directory.scala
@@ -13,13 +13,13 @@
val GitBucketConf = new File(GitBucketHome, "gitbucket.conf")
- val RepositoryHome = "%s/repositories".format(GitBucketHome)
+ val RepositoryHome = s"${GitBucketHome}/repositories"
/**
* Repository names of the specified user.
*/
def getRepositories(owner: String): List[String] = {
- val dir = new File("%s/%s".format(RepositoryHome, owner))
+ val dir = new File(s"${RepositoryHome}/${owner}")
if(dir.exists){
dir.listFiles.filter { file =>
file.isDirectory && !file.getName.endsWith(".wiki.git")
@@ -33,24 +33,24 @@
* Substance directory of the repository.
*/
def getRepositoryDir(owner: String, repository: String): File =
- new File("%s/%s/%s.git".format(RepositoryHome, owner, repository))
+ new File(s"${RepositoryHome}/${owner}/${repository}.git")
/**
* Directory for uploaded files by the specified user.
*/
- def getUserUploadDir(userName: String): File = new File("%s/data/%s/files".format(GitBucketHome, userName))
+ def getUserUploadDir(userName: String): File = new File(s"${GitBucketHome}/data/${userName}/files")
/**
* Root of temporary directories for the specified repository.
*/
def getTemporaryDir(owner: String, repository: String): File =
- new File("%s/tmp/%s/%s".format(GitBucketHome, owner, repository))
+ new File(s"${GitBucketHome}/tmp/${owner}/${repository}")
/**
* Temporary directory which is used to create an archive to download repository contents.
*/
def getDownloadWorkDir(owner: String, repository: String, sessionId: String): File =
- new File(getTemporaryDir(owner, repository), "download/%s".format(sessionId))
+ new File(getTemporaryDir(owner, repository), s"download/${sessionId}")
/**
* Temporary directory which is used in the repository creation.
@@ -65,7 +65,7 @@
* Substance directory of the wiki repository.
*/
def getWikiRepositoryDir(owner: String, repository: String): File =
- new File("%s/%s/%s.wiki.git".format(Directory.RepositoryHome, owner, repository))
+ new File(s"${RepositoryHome}/${owner}/${repository}.wiki.git")
/**
* Wiki working directory which is cloned from the wiki repository.
diff --git a/src/main/scala/util/FileUploadUtil.scala b/src/main/scala/util/FileUploadUtil.scala
index 61f2718..6efacc1 100644
--- a/src/main/scala/util/FileUploadUtil.scala
+++ b/src/main/scala/util/FileUploadUtil.scala
@@ -11,7 +11,7 @@
new SimpleDateFormat("yyyyMMddHHmmSSsss").format(new java.util.Date(System.currentTimeMillis))
def TemporaryDir(implicit session: HttpSession): java.io.File =
- new java.io.File(GitBucketHome, "tmp/_upload/%s".format(session.getId))
+ new java.io.File(GitBucketHome, s"tmp/_upload/${session.getId}")
def getTemporaryFile(fileId: String)(implicit session: HttpSession): java.io.File =
new java.io.File(TemporaryDir, fileId)
diff --git a/src/main/scala/util/Implicits.scala b/src/main/scala/util/Implicits.scala
index 1de9e24..c475136 100644
--- a/src/main/scala/util/Implicits.scala
+++ b/src/main/scala/util/Implicits.scala
@@ -1,7 +1,7 @@
package util
-import twirl.api.Html
import scala.slick.driver.H2Driver.simple._
+import scala.util.matching.Regex
/**
* Provides some usable implicit conversions.
@@ -30,4 +30,23 @@
def &&(c2: => Column[Boolean], guard: => Boolean): Column[Boolean] = if(guard) c1 && c2 else c1
}
+ implicit class RichString(value: String){
+ def replaceBy(regex: Regex)(replace: Regex.MatchData => Option[String]): String = {
+ val sb = new StringBuilder()
+ var i = 0
+ regex.findAllIn(value).matchData.foreach { m =>
+ sb.append(value.substring(i, m.start))
+ i = m.end
+ replace(m) match {
+ case Some(s) => sb.append(s)
+ case None => sb.append(m.matched)
+ }
+ }
+ if(i < value.length){
+ sb.append(value.substring(i))
+ }
+ sb.toString
+ }
+ }
+
}
\ No newline at end of file
diff --git a/src/main/scala/util/JGitUtil.scala b/src/main/scala/util/JGitUtil.scala
index 3cec721..b2ce900 100644
--- a/src/main/scala/util/JGitUtil.scala
+++ b/src/main/scala/util/JGitUtil.scala
@@ -153,7 +153,7 @@
}
RepositoryInfo(
- owner, repository, baseUrl + "/git/%s/%s.git".format(owner, repository),
+ owner, repository, s"${baseUrl}/git/${owner}/${repository}.git",
// commit count
commitCount,
// branches
@@ -169,7 +169,7 @@
} catch {
// not initialized
case e: NoHeadException => RepositoryInfo(
- owner, repository, baseUrl + "/git/%s/%s.git".format(owner, repository), 0, Nil, Nil)
+ owner, repository, s"${baseUrl}/git/${owner}/${repository}.git", 0, Nil, Nil)
}
}
@@ -253,7 +253,7 @@
* @param path filters by this path. default is no filter.
* @return a tuple of the commit list and whether has next
*/
- def getCommitLog(git: Git, revision: String, page: Int = 1, limit: Int = 0, path: String = ""): (List[CommitInfo], Boolean) = {
+ def getCommitLog(git: Git, revision: String, page: Int = 1, limit: Int = 0, path: String = ""): Either[String, (List[CommitInfo], Boolean)] = {
val fixedPage = if(page <= 0) 1 else page
@scala.annotation.tailrec
@@ -267,20 +267,25 @@
}
val revWalk = new RevWalk(git.getRepository)
- revWalk.markStart(revWalk.parseCommit(git.getRepository.resolve(revision)))
- if(path.nonEmpty){
- revWalk.setRevFilter(new RevFilter(){
- def include(walk: RevWalk, commit: RevCommit): Boolean = {
- getDiffs(git, commit.getName, false).find(_.newPath == path).nonEmpty
- }
- override def clone(): RevFilter = this
- })
+ val objectId = git.getRepository.resolve(revision)
+ if(objectId == null){
+ Left(s"${revision} can't be resolved.")
+ } else {
+ revWalk.markStart(revWalk.parseCommit(objectId))
+ if(path.nonEmpty){
+ revWalk.setRevFilter(new RevFilter(){
+ def include(walk: RevWalk, commit: RevCommit): Boolean = {
+ getDiffs(git, commit.getName, false).find(_.newPath == path).nonEmpty
+ }
+ override def clone(): RevFilter = this
+ })
+ }
+
+ val commits = getCommitLog(revWalk.iterator, 0, Nil)
+ revWalk.release
+
+ Right(commits)
}
-
- val commits = getCommitLog(revWalk.iterator, 0, Nil)
- revWalk.release
-
- commits
}
/**
diff --git a/src/main/scala/util/StringUtil.scala b/src/main/scala/util/StringUtil.scala
index 9cce293..ae6d868 100644
--- a/src/main/scala/util/StringUtil.scala
+++ b/src/main/scala/util/StringUtil.scala
@@ -1,5 +1,7 @@
package util
+import java.net.{URLDecoder, URLEncoder}
+
object StringUtil {
def sha1(value: String): String = {
@@ -14,4 +16,8 @@
md.digest.map(b => "%02x".format(b)).mkString
}
+ def urlEncode(value: String): String = URLEncoder.encode(value, "UTF-8")
+
+ def urlDecode(value: String): String = URLDecoder.decode(value, "UTF-8")
+
}
diff --git a/src/main/scala/util/Validations.scala b/src/main/scala/util/Validations.scala
index a518978..1d42d99 100644
--- a/src/main/scala/util/Validations.scala
+++ b/src/main/scala/util/Validations.scala
@@ -11,9 +11,9 @@
def identifier: Constraint = new Constraint(){
def validate(name: String, value: String): Option[String] =
if(!value.matches("^[a-zA-Z0-9\\-_]+$")){
- Some("%s contains invalid character.".format(name))
+ Some(s"${name} contains invalid character.")
} else if(value.startsWith("_") || value.startsWith("-")){
- Some("%s starts with invalid character.".format(name))
+ Some(s"${name} starts with invalid character.")
} else {
None
}
diff --git a/src/main/scala/view/AvatarImageProvider.scala b/src/main/scala/view/AvatarImageProvider.scala
new file mode 100644
index 0000000..33ac699
--- /dev/null
+++ b/src/main/scala/view/AvatarImageProvider.scala
@@ -0,0 +1,26 @@
+package view
+
+import service.RequestCache
+import twirl.api.Html
+import util.StringUtil
+
+trait AvatarImageProvider { self: RequestCache =>
+
+ /**
+ * Returns <img> which displays the avatar icon.
+ * Looks up Gravatar if avatar icon has not been configured in user settings.
+ */
+ protected def getAvatarImageHtml(userName: String, size: Int, tooltip: Boolean = false)(implicit context: app.Context): Html = {
+ val src = getAccountByUserName(userName).collect { case account if(account.image.isEmpty) =>
+ s"""http://www.gravatar.com/avatar/${StringUtil.md5(account.mailAddress)}?s=${size}"""
+ } getOrElse {
+ s"""${context.path}/${userName}/_avatar"""
+ }
+ if(tooltip){
+ Html(s"""""")
+ } else {
+ Html(s"""
""")
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/scala/view/LinkConverter.scala b/src/main/scala/view/LinkConverter.scala
new file mode 100644
index 0000000..968f34f
--- /dev/null
+++ b/src/main/scala/view/LinkConverter.scala
@@ -0,0 +1,33 @@
+package view
+
+import service.RequestCache
+import util.Implicits.RichString
+
+trait LinkConverter { self: RequestCache =>
+
+ /**
+ * Converts issue id, username and commit id to link.
+ */
+ protected def convertRefsLinks(value: String, repository: service.RepositoryService.RepositoryInfo,
+ issueIdPrefix: String = "#")(implicit context: app.Context): String = {
+ value
+ // escape HTML tags
+ .replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """)
+ // convert issue id to link
+ .replaceBy(("(^|\\W)" + issueIdPrefix + "(\\d+)(\\W|$)").r){ m =>
+ if(getIssue(repository.owner, repository.name, m.group(2)).isDefined){
+ Some(s"""${m.group(1)}#${m.group(2)}${m.group(3)}""")
+ } else {
+ Some(s"""${m.group(1)}#${m.group(2)}${m.group(3)}""")
+ }
+ }
+ // convert @username to link
+ .replaceBy("(^|\\W)@([a-zA-Z0-9\\-_]+)(\\W|$)".r){ m =>
+ getAccountByUserName(m.group(2)).map { _ =>
+ s"""${m.group(1)}@${m.group(2)}${m.group(3)}"""
+ }
+ }
+ // convert commit id to link
+ .replaceAll("(^|\\W)([a-f0-9]{40})(\\W|$)", s"""$$1$$2$$3""")
+ }
+}
diff --git a/src/main/scala/view/Markdown.scala b/src/main/scala/view/Markdown.scala
index c8eb148..32cd513 100644
--- a/src/main/scala/view/Markdown.scala
+++ b/src/main/scala/view/Markdown.scala
@@ -1,10 +1,12 @@
package view
+import util.StringUtil
import org.parboiled.common.StringUtils
import org.pegdown._
import org.pegdown.ast._
import org.pegdown.LinkRenderer.Rendering
import scala.collection.JavaConverters._
+import service.RequestCache
object Markdown {
@@ -12,12 +14,17 @@
* Converts Markdown of Wiki pages to HTML.
*/
def toHtml(markdown: String, repository: service.RepositoryService.RepositoryInfo,
- enableWikiLink: Boolean, enableCommitLink: Boolean, enableIssueLink: Boolean)(implicit context: app.Context): String = {
+ enableWikiLink: Boolean, enableRefsLink: Boolean)(implicit context: app.Context): String = {
+ // escape issue id
+ val source = if(enableRefsLink){
+ markdown.replaceAll("(^|\\W)#([0-9]+)(\\W|$)", "$1issue:$2$3")
+ } else markdown
+
val rootNode = new PegDownProcessor(
Extensions.AUTOLINKS | Extensions.WIKILINKS | Extensions.FENCED_CODE_BLOCKS | Extensions.TABLES
- ).parseMarkdown(markdown.toCharArray)
+ ).parseMarkdown(source.toCharArray)
- new GitBucketHtmlSerializer(markdown, context, repository, enableWikiLink, enableCommitLink, enableIssueLink).toHtml(rootNode)
+ new GitBucketHtmlSerializer(markdown, repository, enableWikiLink, enableRefsLink).toHtml(rootNode)
}
}
@@ -33,11 +40,10 @@
} else {
(text, text)
}
- val url = repository.url.replaceFirst("/git/", "/").replaceFirst("\\.git$", "") +
- "/wiki/" + java.net.URLEncoder.encode(page.replace(' ', '-'), "UTF-8")
+ val url = repository.url.replaceFirst("/git/", "/").replaceFirst("\\.git$", "") + "/wiki/" + StringUtil.urlEncode(page)
new Rendering(url, label)
} catch {
- case e: java.io.UnsupportedEncodingException => throw new IllegalStateException();
+ case e: java.io.UnsupportedEncodingException => throw new IllegalStateException
}
} else {
super.render(node)
@@ -64,15 +70,13 @@
class GitBucketHtmlSerializer(
markdown: String,
- context: app.Context,
repository: service.RepositoryService.RepositoryInfo,
enableWikiLink: Boolean,
- enableCommitLink: Boolean,
- enableIssueLink: Boolean
- ) extends ToHtmlSerializer(
+ enableRefsLink: Boolean
+ )(implicit val context: app.Context) extends ToHtmlSerializer(
new GitBucketLinkRender(context, repository, enableWikiLink),
Map[String, VerbatimSerializer](VerbatimSerializer.DEFAULT -> new GitBucketVerbatimSerializer).asJava
- ) {
+ ) with LinkConverter with RequestCache {
override protected def printImageTag(imageNode: SuperNode, url: String): Unit =
printer.print("
")
@@ -99,10 +103,8 @@
}
override def visit(node: TextNode) {
- // convert commit id to link.
- val text = if(enableCommitLink) node.getText.replaceAll("(^|\\W)([0-9a-f]{40})(\\W|$)",
- "$2".format(context.path, repository.owner, repository.name))
- else node.getText
+ // convert commit id and username to link.
+ val text = if(enableRefsLink) convertRefsLinks(node.getText, repository, "issue:") else node.getText
if (abbreviations.isEmpty) {
printer.print(text)
@@ -111,15 +113,4 @@
}
}
- override def visit(node: HeaderNode) {
- val text = markdown.substring(node.getStartIndex, node.getEndIndex - 1).trim
- if(enableIssueLink && text.matches("#[\\d]+")){
- // convert issue id to link
- val issueId = text.substring(1).toInt
- printer.print("#%d".format(context.path, repository.owner, repository.name, issueId, issueId))
- } else {
- printTag(node, "h" + node.getLevel)
- }
- }
-
}
\ No newline at end of file
diff --git a/src/main/scala/view/helpers.scala b/src/main/scala/view/helpers.scala
index b12f644..fa276a2 100644
--- a/src/main/scala/view/helpers.scala
+++ b/src/main/scala/view/helpers.scala
@@ -3,12 +3,12 @@
import java.text.SimpleDateFormat
import twirl.api.Html
import util.StringUtil
-import service.AccountService
+import service.RequestCache
/**
* Provides helper methods for Twirl templates.
*/
-object helpers {
+object helpers extends AvatarImageProvider with LinkConverter with RequestCache {
/**
* Format java.util.Date to "yyyy-MM-dd HH:mm:ss".
@@ -31,13 +31,24 @@
* Converts Markdown of Wiki pages to HTML.
*/
def markdown(value: String, repository: service.RepositoryService.RepositoryInfo,
- enableWikiLink: Boolean, enableCommitLink: Boolean, enableIssueLink: Boolean)(implicit context: app.Context): Html = {
- Html(Markdown.toHtml(value, repository, enableWikiLink, enableCommitLink, enableIssueLink))
+ enableWikiLink: Boolean, enableRefsLink: Boolean)(implicit context: app.Context): Html = {
+ Html(Markdown.toHtml(value, repository, enableWikiLink, enableRefsLink))
}
- def activityMessage(message: String)(implicit context: app.Context): Html = {
- val a = s"a $message aa $$1 a"
-
+ /**
+ * Returns <img> which displays the avatar icon.
+ * Looks up Gravatar if avatar icon has not been configured in user settings.
+ */
+ def avatar(userName: String, size: Int, tooltip: Boolean = false)(implicit context: app.Context): Html =
+ getAvatarImageHtml(userName, size, tooltip)
+
+ /**
+ * Converts commit id, issue id and username to the link.
+ */
+ def link(value: String, repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context): Html =
+ Html(convertRefsLinks(value, repository))
+
+ def activityMessage(message: String)(implicit context: app.Context): Html =
Html(message
.replaceAll("\\[issue:([^\\s]+?)/([^\\s]+?)#((\\d+))\\]" , s"""$$1/$$2#$$3""")
.replaceAll("\\[repo:([^\\s]+?)/([^\\s]+?)\\]" , s"""$$1/$$2""")
@@ -45,56 +56,29 @@
.replaceAll("\\[tag:([^\\s]+?)/([^\\s]+?)#([^\\s]+?)\\]" , s"""$$3""")
.replaceAll("\\[user:([^\\s]+?)\\]" , s"""$$1""")
)
- }
+
+ def urlEncode(value: String): String = StringUtil.urlEncode(value)
+
+ def urlEncode(value: Option[String]): String = value.map(urlEncode).getOrElse("")
/**
* Generates the url to the repository.
*/
def url(repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context): String =
- "%s/%s/%s".format(context.path, repository.owner, repository.name)
+ s"${context.path}/${repository.owner}/${repository.name}"
/**
* Generates the url to the account page.
*/
- def url(userName: String)(implicit context: app.Context): String = "%s/%s".format(context.path, userName)
+ def url(userName: String)(implicit context: app.Context): String =
+ s"${context.path}/${userName}"
/**
* Returns the url to the root of assets.
*/
- def assets(implicit context: app.Context): String = "%s/assets".format(context.path)
+ def assets(implicit context: app.Context): String =
+ s"${context.path}/assets"
- /**
- * Converts issue id and commit id to link.
- */
- def link(value: String, repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context): Html =
- Html(value
- // escape HTML tags
- .replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """)
- // convert issue id to link
- .replaceAll("(^|\\W)#(\\d+)(\\W|$)", "$1#$2$3".format(context.path, repository.owner, repository.name))
- // convert commit id to link
- .replaceAll("(^|\\W)([a-f0-9]{40})(\\W|$)", "$1$2$3").format(context.path, repository.owner, repository.name))
-
-
- /**
- * Returns <img> which displays the avatar icon.
- * Looks up Gravatar if avatar icon has not been configured in user settings.
- */
- def avatar(userName: String, size: Int, tooltip: Boolean = false)(implicit context: app.Context): Html = {
- val account = context.cache(s"account.${userName}"){
- new AccountService {}.getAccountByUserName(userName)
- }
- val src = account.collect { case account if(account.image.isEmpty) =>
- s"""http://www.gravatar.com/avatar/${StringUtil.md5(account.mailAddress)}?s=${size}"""
- } getOrElse {
- s"""${context.path}/${userName}/_avatar"""
- }
- if(tooltip){
- Html(s"""
""")
- } else {
- Html(s"""
""")
- }
- }
/**
* Implicit conversion to add mkHtml() to Seq[Html].
diff --git a/src/main/twirl/helper/activities.scala.html b/src/main/twirl/helper/activities.scala.html
index 93519b4..6b1662c 100644
--- a/src/main/twirl/helper/activities.scala.html
+++ b/src/main/twirl/helper/activities.scala.html
@@ -14,10 +14,10 @@
@activity.additionalInfo.map { additionalInfo =>
@(activity.activityType match {
case "create_wiki" => {
-