mirror of
https://github.com/gitbucket/gitbucket.git
synced 2025-11-08 14:35:52 +01:00
Merge branch 'master' into newui-for-dashboard
Conflicts: src/main/twirl/dashboard/issueslist.scala.html src/main/twirl/dashboard/pullslist.scala.html
This commit is contained in:
@@ -195,7 +195,7 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
org.json4s.jackson.Serialization.write(
|
||||
Map("title" -> x.title,
|
||||
"content" -> view.Markdown.toHtml(x.content getOrElse "No description given.",
|
||||
repository, false, true)
|
||||
repository, false, true, true, isEditable(x.userName, x.repositoryName, x.openedUserName))
|
||||
))
|
||||
}
|
||||
} else Unauthorized
|
||||
@@ -212,7 +212,7 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
contentType = formats("json")
|
||||
org.json4s.jackson.Serialization.write(
|
||||
Map("content" -> view.Markdown.toHtml(x.content,
|
||||
repository, false, true)
|
||||
repository, false, true, true, isEditable(x.userName, x.repositoryName, x.commentedUserName))
|
||||
))
|
||||
}
|
||||
} else Unauthorized
|
||||
|
||||
@@ -77,7 +77,9 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
contentType = "text/html"
|
||||
view.helpers.markdown(params("content"), repository,
|
||||
params("enableWikiLink").toBoolean,
|
||||
params("enableRefsLink").toBoolean)
|
||||
params("enableRefsLink").toBoolean,
|
||||
params("enableTaskList").toBoolean,
|
||||
hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
||||
})
|
||||
|
||||
/**
|
||||
@@ -112,7 +114,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
repo.html.commits(if(path.isEmpty) Nil else path.split("/").toList, branchName, repository,
|
||||
logs.splitWith{ (commit1, commit2) =>
|
||||
view.helpers.date(commit1.commitTime) == view.helpers.date(commit2.commitTime)
|
||||
}, page, hasNext)
|
||||
}, page, hasNext, hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
||||
case Left(_) => NotFound
|
||||
}
|
||||
}
|
||||
@@ -239,6 +241,24 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Creates a branch.
|
||||
*/
|
||||
post("/:owner/:repository/branches")(collaboratorsOnly { repository =>
|
||||
val newBranchName = params.getOrElse("new", halt(400))
|
||||
val fromBranchName = params.getOrElse("from", halt(400))
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
||||
JGitUtil.createBranch(git, fromBranchName, newBranchName)
|
||||
} match {
|
||||
case Right(message) =>
|
||||
flash += "info" -> message
|
||||
redirect(s"/${repository.owner}/${repository.name}/tree/${StringUtil.urlEncode(newBranchName).replace("%2F", "/")}")
|
||||
case Left(message) =>
|
||||
flash += "error" -> message
|
||||
redirect(s"/${repository.owner}/${repository.name}/tree/${fromBranchName}")
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Deletes branch.
|
||||
*/
|
||||
@@ -331,7 +351,8 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
repo.html.files(revision, repository,
|
||||
if(path == ".") Nil else path.split("/").toList, // current path
|
||||
new JGitUtil.CommitInfo(lastModifiedCommit), // last modified commit
|
||||
files, readme, hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
||||
files, readme, hasWritePermission(repository.owner, repository.name, context.loginAccount),
|
||||
flash.get("info"), flash.get("error"))
|
||||
}
|
||||
} getOrElse NotFound
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ trait UserManagementControllerBase extends AccountManagementControllerBase {
|
||||
"url" -> trim(label("URL" ,optional(text(maxlength(200))))),
|
||||
"fileId" -> trim(label("File ID" ,optional(text()))),
|
||||
"clearImage" -> trim(label("Clear image" ,boolean())),
|
||||
"removed" -> trim(label("Disable" ,boolean()))
|
||||
"removed" -> trim(label("Disable" ,boolean(disableByNotYourself("userName"))))
|
||||
)(EditUserForm.apply)
|
||||
|
||||
val newGroupForm = mapping(
|
||||
@@ -190,4 +190,14 @@ trait UserManagementControllerBase extends AccountManagementControllerBase {
|
||||
}
|
||||
}
|
||||
|
||||
protected def disableByNotYourself(paramName: String): Constraint = new Constraint() {
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] = {
|
||||
params.get(paramName).flatMap { userName =>
|
||||
if(userName == context.loginAccount.get.userName)
|
||||
Some("You can't disable your account yourself")
|
||||
else
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import org.eclipse.jgit.treewalk.filter._
|
||||
import org.eclipse.jgit.diff.DiffEntry.ChangeType
|
||||
import org.eclipse.jgit.errors.{ConfigInvalidException, MissingObjectException}
|
||||
import java.util.Date
|
||||
import org.eclipse.jgit.api.errors.NoHeadException
|
||||
import org.eclipse.jgit.api.errors.{JGitInternalException, InvalidRefNameException, RefAlreadyExistsException, NoHeadException}
|
||||
import service.RepositoryService
|
||||
import org.eclipse.jgit.dircache.DirCacheEntry
|
||||
import org.slf4j.LoggerFactory
|
||||
@@ -507,6 +507,17 @@ object JGitUtil {
|
||||
}.find(_._1 != null)
|
||||
}
|
||||
|
||||
def createBranch(git: Git, fromBranch: String, newBranch: String) = {
|
||||
try {
|
||||
git.branchCreate().setStartPoint(fromBranch).setName(newBranch).call()
|
||||
Right("Branch created.")
|
||||
} catch {
|
||||
case e: RefAlreadyExistsException => Left("Sorry, that branch already exists.")
|
||||
// JGitInternalException occurs when new branch name is 'a' and the branch whose name is 'a/*' exists.
|
||||
case _: InvalidRefNameException | _: JGitInternalException => Left("Sorry, that name is invalid.")
|
||||
}
|
||||
}
|
||||
|
||||
def createDirCacheEntry(path: String, mode: FileMode, objectId: ObjectId): DirCacheEntry = {
|
||||
val entry = new DirCacheEntry(path)
|
||||
entry.setFileMode(mode)
|
||||
|
||||
@@ -9,6 +9,7 @@ import org.pegdown.ast._
|
||||
import org.pegdown.LinkRenderer.Rendering
|
||||
import java.text.Normalizer
|
||||
import java.util.Locale
|
||||
import java.util.regex.Pattern
|
||||
import scala.collection.JavaConverters._
|
||||
import service.{RequestCache, WikiService}
|
||||
|
||||
@@ -18,17 +19,23 @@ object Markdown {
|
||||
* Converts Markdown of Wiki pages to HTML.
|
||||
*/
|
||||
def toHtml(markdown: String, repository: service.RepositoryService.RepositoryInfo,
|
||||
enableWikiLink: Boolean, enableRefsLink: Boolean)(implicit context: app.Context): String = {
|
||||
enableWikiLink: Boolean, enableRefsLink: Boolean,
|
||||
enableTaskList: Boolean = false, hasWritePermission: Boolean = false)(implicit context: app.Context): String = {
|
||||
// escape issue id
|
||||
val source = if(enableRefsLink){
|
||||
val s = if(enableRefsLink){
|
||||
markdown.replaceAll("(?<=(\\W|^))#(\\d+)(?=(\\W|$))", "issue:$2")
|
||||
} else markdown
|
||||
|
||||
// escape task list
|
||||
val source = if(enableTaskList){
|
||||
GitBucketHtmlSerializer.escapeTaskList(s)
|
||||
} else s
|
||||
|
||||
val rootNode = new PegDownProcessor(
|
||||
Extensions.AUTOLINKS | Extensions.WIKILINKS | Extensions.FENCED_CODE_BLOCKS | Extensions.TABLES | Extensions.HARDWRAPS
|
||||
).parseMarkdown(source.toCharArray)
|
||||
|
||||
new GitBucketHtmlSerializer(markdown, repository, enableWikiLink, enableRefsLink).toHtml(rootNode)
|
||||
new GitBucketHtmlSerializer(markdown, repository, enableWikiLink, enableRefsLink, enableTaskList, hasWritePermission).toHtml(rootNode)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +89,9 @@ class GitBucketHtmlSerializer(
|
||||
markdown: String,
|
||||
repository: service.RepositoryService.RepositoryInfo,
|
||||
enableWikiLink: Boolean,
|
||||
enableRefsLink: Boolean
|
||||
enableRefsLink: Boolean,
|
||||
enableTaskList: Boolean,
|
||||
hasWritePermission: Boolean
|
||||
)(implicit val context: app.Context) extends ToHtmlSerializer(
|
||||
new GitBucketLinkRender(context, repository, enableWikiLink),
|
||||
Map[String, VerbatimSerializer](VerbatimSerializer.DEFAULT -> new GitBucketVerbatimSerializer).asJava
|
||||
@@ -143,7 +152,10 @@ class GitBucketHtmlSerializer(
|
||||
|
||||
override def visit(node: TextNode): Unit = {
|
||||
// convert commit id and username to link.
|
||||
val text = if(enableRefsLink) convertRefsLinks(node.getText, repository, "issue:") else node.getText
|
||||
val t = if(enableRefsLink) convertRefsLinks(node.getText, repository, "issue:") else node.getText
|
||||
|
||||
// convert task list to checkbox.
|
||||
val text = if(enableTaskList) GitBucketHtmlSerializer.convertCheckBox(t, hasWritePermission) else t
|
||||
|
||||
if (abbreviations.isEmpty) {
|
||||
printer.print(text)
|
||||
@@ -151,6 +163,28 @@ class GitBucketHtmlSerializer(
|
||||
printWithAbbreviations(text)
|
||||
}
|
||||
}
|
||||
|
||||
override def visit(node: BulletListNode): Unit = {
|
||||
if (printChildrenToString(node).contains("""class="task-list-item-checkbox" """)) {
|
||||
printer.println().print("""<ul class="task-list">""").indent(+2)
|
||||
visitChildren(node)
|
||||
printer.indent(-2).println().print("</ul>")
|
||||
} else {
|
||||
printIndentedTag(node, "ul")
|
||||
}
|
||||
}
|
||||
|
||||
override def visit(node: ListItemNode): Unit = {
|
||||
if (printChildrenToString(node).contains("""class="task-list-item-checkbox" """)) {
|
||||
printer.println()
|
||||
printer.print("""<li class="task-list-item">""")
|
||||
visitChildren(node)
|
||||
printer.print("</li>")
|
||||
} else {
|
||||
printer.println()
|
||||
printTag(node, "li")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object GitBucketHtmlSerializer {
|
||||
@@ -163,4 +197,14 @@ object GitBucketHtmlSerializer {
|
||||
val noSpecialChars = StringUtil.urlEncode(normalized)
|
||||
noSpecialChars.toLowerCase(Locale.ENGLISH)
|
||||
}
|
||||
|
||||
def escapeTaskList(text: String): String = {
|
||||
Pattern.compile("""^( *)- \[([x| ])\] """, Pattern.MULTILINE).matcher(text).replaceAll("$1* task:$2: ")
|
||||
}
|
||||
|
||||
def convertCheckBox(text: String, hasWritePermission: Boolean): String = {
|
||||
val disabled = if (hasWritePermission) "" else "disabled"
|
||||
text.replaceAll("task:x:", """<input type="checkbox" class="task-list-item-checkbox" checked="checked" """ + disabled + "/>")
|
||||
.replaceAll("task: :", """<input type="checkbox" class="task-list-item-checkbox" """ + disabled + "/>")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
package view
|
||||
import java.util.{Date, TimeZone}
|
||||
import java.util.{Locale, Date, TimeZone}
|
||||
import java.text.SimpleDateFormat
|
||||
import play.twirl.api.Html
|
||||
import util.StringUtil
|
||||
@@ -15,6 +15,47 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
|
||||
*/
|
||||
def datetime(date: Date): String = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date)
|
||||
|
||||
val timeUnits = List(
|
||||
(1000L, "second"),
|
||||
(1000L * 60, "minute"),
|
||||
(1000L * 60 * 60, "hour"),
|
||||
(1000L * 60 * 60 * 24, "day"),
|
||||
(1000L * 60 * 60 * 24 * 30, "month"),
|
||||
(1000L * 60 * 60 * 24 * 365, "year")
|
||||
).reverse
|
||||
|
||||
/**
|
||||
* Format java.util.Date to "x {seconds/minutes/hours/days/months/years} ago"
|
||||
*/
|
||||
def datetimeAgo(date: Date): String = {
|
||||
val duration = new Date().getTime - date.getTime
|
||||
timeUnits.find(tuple => duration / tuple._1 > 0) match {
|
||||
case Some((unitValue, unitString)) =>
|
||||
val value = duration / unitValue
|
||||
s"${value} ${unitString}${if (value > 1) "s" else ""} ago"
|
||||
case None => "just now"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Format java.util.Date to "x {seconds/minutes/hours/days} ago"
|
||||
* If duration over 1 month, format to "d MMM (yyyy)"
|
||||
*
|
||||
*/
|
||||
def datetimeAgoRecentOnly(date: Date): String = {
|
||||
val duration = new Date().getTime - date.getTime
|
||||
timeUnits.find(tuple => duration / tuple._1 > 0) match {
|
||||
case Some((_, "month")) => s"on ${new SimpleDateFormat("d MMM", Locale.ENGLISH).format(date)}"
|
||||
case Some((_, "year")) => s"on ${new SimpleDateFormat("d MMM yyyy", Locale.ENGLISH).format(date)}"
|
||||
case Some((unitValue, unitString)) =>
|
||||
val value = duration / unitValue
|
||||
s"${value} ${unitString}${if (value > 1) "s" else ""} ago"
|
||||
case None => "just now"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Format java.util.Date to "yyyy-MM-dd'T'hh:mm:ss'Z'".
|
||||
*/
|
||||
@@ -48,8 +89,8 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
|
||||
* Converts Markdown of Wiki pages to HTML.
|
||||
*/
|
||||
def markdown(value: String, repository: service.RepositoryService.RepositoryInfo,
|
||||
enableWikiLink: Boolean, enableRefsLink: Boolean)(implicit context: app.Context): Html =
|
||||
Html(Markdown.toHtml(value, repository, enableWikiLink, enableRefsLink))
|
||||
enableWikiLink: Boolean, enableRefsLink: Boolean, enableTaskList: Boolean = false, hasWritePermission: Boolean = false)(implicit context: app.Context): Html =
|
||||
Html(Markdown.toHtml(value, repository, enableWikiLink, enableRefsLink, enableTaskList, hasWritePermission))
|
||||
|
||||
def renderMarkup(filePath: List[String], fileContent: String, branch: String,
|
||||
repository: service.RepositoryService.RepositoryInfo,
|
||||
|
||||
Reference in New Issue
Block a user