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("\"").printEncoded(printChildrenToString(imageNode)).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" => { -
Created {additionalInfo}.
+
Created {additionalInfo}.
} case "edit_wiki" => { -
Edited {additionalInfo}.
+
Edited {additionalInfo}.
} case "push" => {
@@ -26,7 +26,7 @@
...
} else {
- {commit.substring(0, 7)} + {commit.substring(0, 7)} {commit.substring(41)}
} diff --git a/src/main/twirl/helper/preview.scala.html b/src/main/twirl/helper/preview.scala.html index 642c3bc..9c1a607 100644 --- a/src/main/twirl/helper/preview.scala.html +++ b/src/main/twirl/helper/preview.scala.html @@ -1,5 +1,5 @@ -@(repository: service.RepositoryService.RepositoryInfo, content: String, enableWikiLink: Boolean, - enableCommitLink: Boolean, enableIssueLink: Boolean, style: String = "", placeholder: String = "Leave a comment")(implicit context: app.Context) +@(repository: service.RepositoryService.RepositoryInfo, content: String, enableWikiLink: Boolean, enableRefsLink: Boolean, + style: String = "", placeholder: String = "Leave a comment")(implicit context: app.Context) @import context._ @import view.helpers._
@@ -30,10 +30,9 @@ $('#preview').click(function(){ $('#preview-area').html(' Previewing...'); $.post('@url(repository)/_preview', { - content : $('#content').val(), - enableWikiLink : @enableWikiLink, - enableCommitLink : @enableCommitLink, - enableIssueLink : @enableIssueLink + content : $('#content').val(), + enableWikiLink : @enableWikiLink, + enableRefsLink : @enableRefsLink }, function(data){ $('#preview-area').html(data); prettyPrint(); diff --git a/src/main/twirl/issues/create.scala.html b/src/main/twirl/issues/create.scala.html index 68d8eff..fec4193 100644 --- a/src/main/twirl/issues/create.scala.html +++ b/src/main/twirl/issues/create.scala.html @@ -43,7 +43,7 @@

- @helper.html.preview(repository, "", false, true, true, "width: 600px; height: 200px;") + @helper.html.preview(repository, "", false, true, "width: 600px; height: 200px;")
diff --git a/src/main/twirl/issues/issue.scala.html b/src/main/twirl/issues/issue.scala.html index 2e35ac3..a12eafe 100644 --- a/src/main/twirl/issues/issue.scala.html +++ b/src/main/twirl/issues/issue.scala.html @@ -64,7 +64,7 @@
- @markdown(issue.content getOrElse "No description given.", repository, false, true, true) + @markdown(issue.content getOrElse "No description given.", repository, false, true)
@@ -82,7 +82,7 @@
- @markdown(comment.content, repository, false, true, true) + @markdown(comment.content, repository, false, true)
@comment.action.map { action => @@ -98,18 +98,18 @@ } } @if(loginAccount.isDefined){ -
+
@avatar(loginAccount.get.userName, 48)
- @helper.html.preview(repository, "", false, true, true, "width: 680px; height: 100px;") + @helper.html.preview(repository, "", false, true, "width: 680px; height: 100px;")
- + @if(hasWritePermission || issue.openedUserName == loginAccount.get.userName){ - + }
diff --git a/src/main/twirl/issues/list.scala.html b/src/main/twirl/issues/list.scala.html index 3fa59f2..16bb6cb 100644 --- a/src/main/twirl/issues/list.scala.html +++ b/src/main/twirl/issues/list.scala.html @@ -1,7 +1,8 @@ @(issues: List[(model.Issue, List[model.Label], Int)], page: Int, - labels: List[model.Label], + collaborators: List[String], milestones: List[model.Milestone], + labels: List[model.Label], openCount: Int, closedCount: Int, allCount: Int, @@ -178,15 +179,16 @@ } else { + @if(hasWritePermission){
- +
@helper.html.dropdown("Label") { @labels.map { label =>
  • - +   @label.labelName @@ -195,24 +197,30 @@ } } @helper.html.dropdown("Assignee") { -
  • Clear assignee
  • +
  • Clear assignee
  • + @collaborators.map { collaborator => +
  • @avatar(collaborator, 20) @collaborator
  • + } } @helper.html.dropdown("Milestone") { -
  • Clear this milestone
  • +
  • Clear this milestone
  • @milestones.map { milestone => -
  • @milestone.title
  • +
  • @milestone.title
  • } } + } } @issues.map { case (issue, labels, commentCount) => + @if(hasWritePermission){