diff --git a/src/main/scala/app/IssuesController.scala b/src/main/scala/app/IssuesController.scala index b6b7bb9..d2bb683 100644 --- a/src/main/scala/app/IssuesController.scala +++ b/src/main/scala/app/IssuesController.scala @@ -195,7 +195,7 @@ 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 @@ 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 diff --git a/src/main/scala/app/RepositoryViewerController.scala b/src/main/scala/app/RepositoryViewerController.scala index 4291730..5b5b11b 100644 --- a/src/main/scala/app/RepositoryViewerController.scala +++ b/src/main/scala/app/RepositoryViewerController.scala @@ -77,7 +77,9 @@ 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 @@ 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 } } @@ -240,6 +242,24 @@ }) /** + * 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. */ get("/:owner/:repository/delete/*")(collaboratorsOnly { repository => @@ -331,7 +351,8 @@ 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 } diff --git a/src/main/scala/app/UserManagementController.scala b/src/main/scala/app/UserManagementController.scala index 6125039..d98d703 100644 --- a/src/main/scala/app/UserManagementController.scala +++ b/src/main/scala/app/UserManagementController.scala @@ -49,7 +49,7 @@ "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 @@ } } + 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 + } + } + } } diff --git a/src/main/scala/util/JGitUtil.scala b/src/main/scala/util/JGitUtil.scala index 0e0493d..5ea43a3 100644 --- a/src/main/scala/util/JGitUtil.scala +++ b/src/main/scala/util/JGitUtil.scala @@ -14,7 +14,7 @@ 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 @@ }.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) diff --git a/src/main/scala/view/Markdown.scala b/src/main/scala/view/Markdown.scala index 0512024..08c3dd4 100644 --- a/src/main/scala/view/Markdown.scala +++ b/src/main/scala/view/Markdown.scala @@ -9,6 +9,7 @@ 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 @@ * 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 @@ 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 @@ 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 @@ printWithAbbreviations(text) } } + + override def visit(node: BulletListNode): Unit = { + if (printChildrenToString(node).contains("""class="task-list-item-checkbox" """)) { + printer.println().print("""") + } else { + printIndentedTag(node, "ul") + } + } + + override def visit(node: ListItemNode): Unit = { + if (printChildrenToString(node).contains("""class="task-list-item-checkbox" """)) { + printer.println() + printer.print("""
  • """) + visitChildren(node) + printer.print("
  • ") + } else { + printer.println() + printTag(node, "li") + } + } } object GitBucketHtmlSerializer { @@ -163,4 +197,14 @@ 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:", """") + .replaceAll("task: :", """") + } } diff --git a/src/main/scala/view/helpers.scala b/src/main/scala/view/helpers.scala index 78f658f..19dd2e8 100644 --- a/src/main/scala/view/helpers.scala +++ b/src/main/scala/view/helpers.scala @@ -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 @@ */ 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 @@ * 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, diff --git a/src/main/twirl/account/repositories.scala.html b/src/main/twirl/account/repositories.scala.html index 1ad52df..8a3706b 100644 --- a/src/main/twirl/account/repositories.scala.html +++ b/src/main/twirl/account/repositories.scala.html @@ -25,7 +25,7 @@ @if(repository.repository.description.isDefined){
    @repository.repository.description
    } -
    Last updated: @datetime(repository.repository.lastActivityDate)
    +
    Updated @helper.html.datetimeago(repository.repository.lastActivityDate)
    } diff --git a/src/main/twirl/admin/users/user.scala.html b/src/main/twirl/admin/users/user.scala.html index fb022c0..1f25857 100644 --- a/src/main/twirl/admin/users/user.scala.html +++ b/src/main/twirl/admin/users/user.scala.html @@ -16,6 +16,9 @@ Disable +
    + +
    } @if(account.map(_.password.nonEmpty).getOrElse(true)){ diff --git a/src/main/twirl/helper/activities.scala.html b/src/main/twirl/helper/activities.scala.html index 980eaf5..9bb3f8d 100644 --- a/src/main/twirl/helper/activities.scala.html +++ b/src/main/twirl/helper/activities.scala.html @@ -62,7 +62,7 @@ @detailActivity(activity: model.Activity, image: String) = {
    -
    @datetime(activity.activityDate)
    +
    @helper.html.datetimeago(activity.activityDate)
    @avatar(activity.activityUserName, 16) @activityMessage(activity.message) @@ -76,7 +76,7 @@ @customActivity(activity: model.Activity, image: String)(additionalInfo: Any) = {
    -
    @datetime(activity.activityDate)
    +
    @helper.html.datetimeago(activity.activityDate)
    @avatar(activity.activityUserName, 16) @activityMessage(activity.message) @@ -91,7 +91,7 @@
    @avatar(activity.activityUserName, 16) @activityMessage(activity.message) - @datetime(activity.activityDate) + @helper.html.datetimeago(activity.activityDate)
    } diff --git a/src/main/twirl/helper/branchcontrol.scala.html b/src/main/twirl/helper/branchcontrol.scala.html new file mode 100644 index 0000000..5381688 --- /dev/null +++ b/src/main/twirl/helper/branchcontrol.scala.html @@ -0,0 +1,62 @@ +@(branch: String = "", + repository: service.RepositoryService.RepositoryInfo, + hasWritePermission: Boolean)(body: Html)(implicit context: app.Context) +@import context._ +@import view.helpers._ +@helper.html.dropdown( + value = if(branch.length == 40) branch.substring(0, 10) else branch, + prefix = if(branch.length == 40) "tree" else if(repository.branchList.contains(branch)) "branch" else "tree", + mini = true +) { +
  • Switch branches
  • +
  • + @body + @if(hasWritePermission) { + + } +} + diff --git a/src/main/twirl/helper/datetimeago.scala.html b/src/main/twirl/helper/datetimeago.scala.html new file mode 100644 index 0000000..3b68f34 --- /dev/null +++ b/src/main/twirl/helper/datetimeago.scala.html @@ -0,0 +1,10 @@ +@(latestUpdatedDate: java.util.Date, + recentOnly: Boolean = true) +@import view.helpers._ + + @if(recentOnly){ + @datetimeAgoRecentOnly(latestUpdatedDate) + }else{ + @datetimeAgo(latestUpdatedDate) + } + diff --git a/src/main/twirl/helper/error.scala.html b/src/main/twirl/helper/error.scala.html new file mode 100644 index 0000000..00f43e2 --- /dev/null +++ b/src/main/twirl/helper/error.scala.html @@ -0,0 +1,7 @@ +@(error: Option[Any]) +@if(error.isDefined){ +
    + + @error +
    +} \ No newline at end of file diff --git a/src/main/twirl/helper/information.scala.html b/src/main/twirl/helper/information.scala.html index d8bcff5..ff382a2 100644 --- a/src/main/twirl/helper/information.scala.html +++ b/src/main/twirl/helper/information.scala.html @@ -1,7 +1,7 @@ @(info: Option[Any]) @if(info.isDefined){
    - + @info
    } diff --git a/src/main/twirl/helper/preview.scala.html b/src/main/twirl/helper/preview.scala.html index b6f37c7..3c0e13e 100644 --- a/src/main/twirl/helper/preview.scala.html +++ b/src/main/twirl/helper/preview.scala.html @@ -1,4 +1,4 @@ -@(repository: service.RepositoryService.RepositoryInfo, content: String, enableWikiLink: Boolean, enableRefsLink: Boolean, +@(repository: service.RepositoryService.RepositoryInfo, content: String, enableWikiLink: Boolean, enableRefsLink: Boolean, enableTaskList: Boolean, hasWritePermission: Boolean, style: String = "", placeholder: String = "Leave a comment", elastic: Boolean = false)(implicit context: app.Context) @import context._ @import view.helpers._ @@ -38,7 +38,8 @@ $.post('@url(repository)/_preview', { content : $('#content').val(), enableWikiLink : @enableWikiLink, - enableRefsLink : @enableRefsLink + enableRefsLink : @enableRefsLink, + enableTaskList : @enableTaskList }, function(data){ $('#preview-area').html(data); prettyPrint(); diff --git a/src/main/twirl/issues/commentform.scala.html b/src/main/twirl/issues/commentform.scala.html index 4ca951e..04169c7 100644 --- a/src/main/twirl/issues/commentform.scala.html +++ b/src/main/twirl/issues/commentform.scala.html @@ -10,7 +10,7 @@
    @avatar(loginAccount.get.userName, 48)
    - @helper.html.preview(repository, "", false, true, "width: 635px; height: 100px; max-height: 150px;", elastic = true) + @helper.html.preview(repository, "", false, true, true, hasWritePermission, "width: 635px; height: 100px; max-height: 150px;", elastic = true)
    @@ -28,4 +28,4 @@ $('').attr('name', 'action').val($(this).val().toLowerCase()).appendTo('form'); }); }); - \ No newline at end of file + diff --git a/src/main/twirl/issues/commentlist.scala.html b/src/main/twirl/issues/commentlist.scala.html index 979062f..b351a1d 100644 --- a/src/main/twirl/issues/commentlist.scala.html +++ b/src/main/twirl/issues/commentlist.scala.html @@ -8,7 +8,7 @@
    @avatar(issue.openedUserName, 48)
    - @user(issue.openedUserName, styleClass="username strong") commented on @datetime(issue.registeredDate) + @user(issue.openedUserName, styleClass="username strong") commented @helper.html.datetimeago(issue.registeredDate) @if(hasWritePermission || loginAccount.map(_.userName == issue.openedUserName).getOrElse(false)){ @@ -16,7 +16,7 @@
    - @markdown(issue.content getOrElse "No description provided.", repository, false, true) + @markdown(issue.content getOrElse "No description provided.", repository, false, true, true, hasWritePermission)
    @@ -32,7 +32,7 @@ } else { @if(pullreq.isEmpty){ referenced the issue } else { referenced the pull request } } - on @datetime(comment.registeredDate) + @helper.html.datetimeago(comment.registeredDate) @if(comment.action != "commit" && comment.action != "merge" && comment.action != "refer" && @@ -46,7 +46,7 @@ @if(comment.action == "commit" && comment.content.split(" ").last.matches("[a-f0-9]{40}")){ @defining(comment.content.substring(comment.content.length - 40)){ id => - @markdown(comment.content.substring(0, comment.content.length - 41), repository, false, true) + @markdown(comment.content.substring(0, comment.content.length - 41), repository, false, true, true, hasWritePermission) } } else { @if(comment.action == "refer"){ @@ -54,7 +54,7 @@ Issue #@issueId: @rest.mkString(":") } } else { - @markdown(comment.content, repository, false, true) + @markdown(comment.content, repository, false, true, true, hasWritePermission) } }
    @@ -70,7 +70,7 @@ } else { @pullreq.map(_.userName):@pullreq.map(_.branch) to @pullreq.map(_.requestUserName):@pullreq.map(_.requestBranch) } - @datetime(comment.registeredDate) + @helper.html.datetimeago(comment.registeredDate)
    } @if(comment.action == "close" || comment.action == "close_comment"){ @@ -78,9 +78,9 @@ Closed @avatar(comment.commentedUserName, 20) @if(issue.isPullRequest){ - @user(comment.commentedUserName, styleClass="username strong") closed the pull request @datetime(comment.registeredDate) + @user(comment.commentedUserName, styleClass="username strong") closed the pull request @helper.html.datetimeago(comment.registeredDate) } else { - @user(comment.commentedUserName, styleClass="username strong") closed the issue @datetime(comment.registeredDate) + @user(comment.commentedUserName, styleClass="username strong") closed the issue @helper.html.datetimeago(comment.registeredDate) }
    } @@ -88,14 +88,14 @@
    Reopened @avatar(comment.commentedUserName, 20) - @user(comment.commentedUserName, styleClass="username strong") reopened the issue @datetime(comment.registeredDate) + @user(comment.commentedUserName, styleClass="username strong") reopened the issue @helper.html.datetimeago(comment.registeredDate)
    } @if(comment.action == "delete_branch"){
    Deleted @avatar(comment.commentedUserName, 20) - @user(comment.commentedUserName, styleClass="username strong") deleted the @pullreq.map(_.requestBranch) branch @datetime(comment.registeredDate) + @user(comment.commentedUserName, styleClass="username strong") deleted the @pullreq.map(_.requestBranch) branch @helper.html.datetimeago(comment.registeredDate)
    } } @@ -134,5 +134,67 @@ } return false; }); + + var extractMarkdown = function(data){ + $('body').append('
    '); + $('#tmp').html(data); + var markdown = $('#tmp textarea').val(); + $('#tmp').remove(); + return markdown; + }; + + var replaceTaskList = function(issueContentHtml, checkboxes) { + var ss = [], + markdown = extractMarkdown(issueContentHtml), + xs = markdown.split(/- \[[x| ]\]/g); + for (var i=0; i \ No newline at end of file + diff --git a/src/main/twirl/issues/create.scala.html b/src/main/twirl/issues/create.scala.html index cf91159..7080667 100644 --- a/src/main/twirl/issues/create.scala.html +++ b/src/main/twirl/issues/create.scala.html @@ -57,7 +57,7 @@

    - @helper.html.preview(repository, "", false, true, "width: 565px; height: 200px; max-height: 250px;", elastic = true) + @helper.html.preview(repository, "", false, true, true, hasWritePermission, "width: 565px; height: 200px; max-height: 250px;", elastic = true)
    diff --git a/src/main/twirl/issues/issue.scala.html b/src/main/twirl/issues/issue.scala.html index 3b45ebf..3b7f125 100644 --- a/src/main/twirl/issues/issue.scala.html +++ b/src/main/twirl/issues/issue.scala.html @@ -28,7 +28,7 @@ Open } - @user(issue.openedUserName, styleClass="username strong") opened this issue on @datetime(issue.registeredDate) - @defining( + @user(issue.openedUserName, styleClass="username strong") opened this issue @helper.html.datetimeago(issue.registeredDate) - @defining( comments.filter( _.action.contains("comment") ).size ){ count => @count @plural(count, "comment") diff --git a/src/main/twirl/issues/listparts.scala.html b/src/main/twirl/issues/listparts.scala.html index d3b5a11..ab9cbad 100644 --- a/src/main/twirl/issues/listparts.scala.html +++ b/src/main/twirl/issues/listparts.scala.html @@ -203,7 +203,7 @@ }
    - #@issue.issueId opened by @user(issue.openedUserName, styleClass="username") @datetime(issue.registeredDate) + #@issue.issueId opened @helper.html.datetimeago(issue.registeredDate) by @user(issue.openedUserName, styleClass="username") @milestone.map { milestone => @milestone } diff --git a/src/main/twirl/issues/milestones/list.scala.html b/src/main/twirl/issues/milestones/list.scala.html index 8770832..84a03a4 100644 --- a/src/main/twirl/issues/milestones/list.scala.html +++ b/src/main/twirl/issues/milestones/list.scala.html @@ -34,7 +34,7 @@ @milestone.title
    @if(milestone.closedDate.isDefined){ - Closed @datetime(milestone.closedDate.get) + Closed @helper.html.datetimeago(milestone.closedDate.get) } else { @milestone.dueDate.map { dueDate => @if(isPast(dueDate)){ diff --git a/src/main/twirl/menu.scala.html b/src/main/twirl/menu.scala.html index 8076c82..7ac1dc8 100644 --- a/src/main/twirl/menu.scala.html +++ b/src/main/twirl/menu.scala.html @@ -1,7 +1,9 @@ @(active: String, repository: service.RepositoryService.RepositoryInfo, id: Option[String] = None, - expand: Boolean = false)(body: Html)(implicit context: app.Context) + expand: Boolean = false, + info: Option[Any] = None, + error: Option[Any] = None)(body: Html)(implicit context: app.Context) @import context._ @import view.helpers._ @@ -31,6 +33,8 @@ }
    + @helper.html.information(info) + @helper.html.error(error) @if(repository.commitCount > 0){
    diff --git a/src/main/twirl/pulls/compare.scala.html b/src/main/twirl/pulls/compare.scala.html index b99e5b2..7d900ab 100644 --- a/src/main/twirl/pulls/compare.scala.html +++ b/src/main/twirl/pulls/compare.scala.html @@ -58,7 +58,7 @@
    - @helper.html.preview(repository, "", false, true, "width: 580px; height: 200px;") + @helper.html.preview(repository, "", false, true, true, hasWritePermission, "width: 580px; height: 200px;") diff --git a/src/main/twirl/pulls/pullreq.scala.html b/src/main/twirl/pulls/pullreq.scala.html index 35cebb3..495379b 100644 --- a/src/main/twirl/pulls/pullreq.scala.html +++ b/src/main/twirl/pulls/pullreq.scala.html @@ -20,7 +20,7 @@ Merged @user(comment.commentedUserName, styleClass="username strong") merged @commits.size @plural(commits.size, "commit") into @pullreq.userName:@pullreq.branch from @pullreq.requestUserName:@pullreq.requestBranch - at @datetime(comment.registeredDate) + @helper.html.datetimeago(comment.registeredDate) }.getOrElse { Closed @user(issue.openedUserName, styleClass="username strong") wants to merge @commits.size @plural(commits.size, "commit") diff --git a/src/main/twirl/repo/blob.scala.html b/src/main/twirl/repo/blob.scala.html index 2c895ac..549d2e8 100644 --- a/src/main/twirl/repo/blob.scala.html +++ b/src/main/twirl/repo/blob.scala.html @@ -9,10 +9,10 @@ @html.main(s"${repository.owner}/${repository.name}", Some(repository)) { @html.menu("code", repository){
    - @helper.html.dropdown( - value = if(branch.length == 40) branch.substring(0, 10) else branch, - prefix = if(branch.length == 40) "tree" else if(repository.branchList.contains(branch)) "branch" else "tree", - mini = true + @helper.html.branchcontrol( + branch, + repository, + hasWritePermission ){ @repository.branchList.map { x =>
  • @helper.html.checkicon(x == branch) @x
  • @@ -34,7 +34,7 @@
    @avatar(latestCommit, 20) @user(latestCommit.authorName, latestCommit.authorEmailAddress, "username strong") - @datetime(latestCommit.commitTime) + @helper.html.datetimeago(latestCommit.commitTime) @link(latestCommit.summary, repository)
    diff --git a/src/main/twirl/repo/branches.scala.html b/src/main/twirl/repo/branches.scala.html index 214c37c..9857684 100644 --- a/src/main/twirl/repo/branches.scala.html +++ b/src/main/twirl/repo/branches.scala.html @@ -22,7 +22,7 @@ } - @datetime(latestUpdateDate) + @helper.html.datetimeago(latestUpdateDate, false) @if(repository.repository.defaultBranch == branchName){ diff --git a/src/main/twirl/repo/commit.scala.html b/src/main/twirl/repo/commit.scala.html index a022236..9b7c2f0 100644 --- a/src/main/twirl/repo/commit.scala.html +++ b/src/main/twirl/repo/commit.scala.html @@ -68,13 +68,13 @@
    @avatar(commit, 20) @user(commit.authorName, commit.authorEmailAddress, "username strong") - authored on @datetime(commit.authorTime) + authored @helper.html.datetimeago(commit.authorTime)
    @if(commit.isDifferentFromAuthor) {
    @user(commit.committerName, commit.committerEmailAddress, "username strong") - committed on @datetime(commit.commitTime) + committed @helper.html.datetimeago(commit.commitTime)
    }
    diff --git a/src/main/twirl/repo/commits.scala.html b/src/main/twirl/repo/commits.scala.html index be2b02f..443ab07 100644 --- a/src/main/twirl/repo/commits.scala.html +++ b/src/main/twirl/repo/commits.scala.html @@ -3,16 +3,17 @@ repository: service.RepositoryService.RepositoryInfo, commits: Seq[Seq[util.JGitUtil.CommitInfo]], page: Int, - hasNext: Boolean)(implicit context: app.Context) + hasNext: Boolean, + hasWritePermission: Boolean)(implicit context: app.Context) @import context._ @import view.helpers._ @html.main(s"${repository.owner}/${repository.name}", Some(repository)) { @html.menu("code", repository){
    - @helper.html.dropdown( - value = if(branch.length == 40) branch.substring(0, 10) else branch, - prefix = if(branch.length == 40) "tree" else if(repository.branchList.contains(branch)) "branch" else "tree", - mini = true + @helper.html.branchcontrol( + branch, + repository, + hasWritePermission ){ @repository.branchList.map { x =>
  • @helper.html.checkicon(x == branch) @x
  • @@ -58,11 +59,11 @@ }
    @user(commit.authorName, commit.authorEmailAddress, "username") - authored @datetime(commit.authorTime) + authored @helper.html.datetimeago(commit.authorTime) @if(commit.isDifferentFromAuthor) { @user(commit.committerName, commit.committerEmailAddress, "username") - committed @datetime(commit.authorTime) + committed @helper.html.datetimeago(commit.authorTime) }
    diff --git a/src/main/twirl/repo/files.scala.html b/src/main/twirl/repo/files.scala.html index 2235d6e..96e4a61 100644 --- a/src/main/twirl/repo/files.scala.html +++ b/src/main/twirl/repo/files.scala.html @@ -4,16 +4,18 @@ latestCommit: util.JGitUtil.CommitInfo, files: List[util.JGitUtil.FileInfo], readme: Option[(List[String], String)], - hasWritePermission: Boolean)(implicit context: app.Context) + hasWritePermission: Boolean, + info: Option[Any] = None, + error: Option[Any] = None)(implicit context: app.Context) @import context._ @import view.helpers._ @html.main(s"${repository.owner}/${repository.name}", Some(repository)) { - @html.menu("code", repository, Some(branch), pathList.isEmpty){ + @html.menu("code", repository, Some(branch), pathList.isEmpty, info, error){
    - @helper.html.dropdown( - value = if(branch.length == 40) branch.substring(0, 10) else branch, - prefix = if(branch.length == 40) "tree" else if(repository.branchList.contains(branch)) "branch" else "tree", - mini = true + @helper.html.branchcontrol( + branch, + repository, + hasWritePermission ){ @repository.branchList.map { x =>
  • @helper.html.checkicon(x == branch) @x
  • @@ -47,13 +49,13 @@
    @avatar(latestCommit, 20) @user(latestCommit.authorName, latestCommit.authorEmailAddress, "username strong") - authored on @datetime(latestCommit.authorTime) + authored @helper.html.datetimeago(latestCommit.authorTime)
    @if(latestCommit.isDifferentFromAuthor) {
    @user(latestCommit.committerName, latestCommit.committerEmailAddress, "username strong") - committed on @datetime(latestCommit.commitTime) + committed @helper.html.datetimeago(latestCommit.commitTime)
    }
    @@ -106,7 +108,7 @@ @link(file.message, repository) [@user(file.author, file.mailAddress)] - @datetime(file.time) + @helper.html.datetimeago(file.time, false) } diff --git a/src/main/twirl/repo/tags.scala.html b/src/main/twirl/repo/tags.scala.html index e5b0fa9..cd611fb 100644 --- a/src/main/twirl/repo/tags.scala.html +++ b/src/main/twirl/repo/tags.scala.html @@ -14,7 +14,7 @@ @repository.tags.reverse.map { tag => @tag.name - @datetime(tag.time) + @helper.html.datetimeago(tag.time, false) @tag.id.substring(0, 10) ZIP diff --git a/src/main/twirl/search/code.scala.html b/src/main/twirl/search/code.scala.html index 20b08a6..04d0a19 100644 --- a/src/main/twirl/search/code.scala.html +++ b/src/main/twirl/search/code.scala.html @@ -16,7 +16,7 @@ @files.drop((page - 1) * CodeLimit).take(CodeLimit).map { file =>
    @file.path
    -
    Latest commit at @datetime(file.lastModified)
    +
    Last commited @helper.html.datetimeago(file.lastModified)
    @Html(file.highlightText)
    } diff --git a/src/main/twirl/search/issues.scala.html b/src/main/twirl/search/issues.scala.html index d381091..7a9c17a 100644 --- a/src/main/twirl/search/issues.scala.html +++ b/src/main/twirl/search/issues.scala.html @@ -22,7 +22,7 @@ }
    Opened by @issue.openedUserName - at @datetime(issue.registeredDate) + @helper.html.datetimeago(issue.registeredDate) @if(issue.commentCount > 0){   @issue.commentCount @plural(issue.commentCount, "comment") } diff --git a/src/main/twirl/wiki/edit.scala.html b/src/main/twirl/wiki/edit.scala.html index b7ca333..36a61dc 100644 --- a/src/main/twirl/wiki/edit.scala.html +++ b/src/main/twirl/wiki/edit.scala.html @@ -22,7 +22,7 @@
    - @helper.html.preview(repository, page.map(_.content).getOrElse(""), true, false, "width: 850px; height: 400px;", "") + @helper.html.preview(repository, page.map(_.content).getOrElse(""), true, false, false, false, "width: 850px; height: 400px;", "") @@ -36,4 +36,4 @@ return confirm('Are you sure you want to delete this page?'); }); }); - \ No newline at end of file + diff --git a/src/main/twirl/wiki/history.scala.html b/src/main/twirl/wiki/history.scala.html index bd2ba49..2052f4c 100644 --- a/src/main/twirl/wiki/history.scala.html +++ b/src/main/twirl/wiki/history.scala.html @@ -36,7 +36,7 @@ @avatar(commit, 20) @user(commit.authorName, commit.authorEmailAddress) - @datetime(commit.authorTime): @commit.shortMessage + @helper.html.datetimeago(commit.authorTime): @commit.shortMessage } diff --git a/src/main/twirl/wiki/page.scala.html b/src/main/twirl/wiki/page.scala.html index 8d103d8..74dabb2 100644 --- a/src/main/twirl/wiki/page.scala.html +++ b/src/main/twirl/wiki/page.scala.html @@ -12,7 +12,7 @@
  • @pageName

    - @page.committer edited this page at @datetime(page.time) + @page.committer edited this page @helper.html.datetimeago(page.time)
  • diff --git a/src/main/webapp/assets/common/css/gitbucket.css b/src/main/webapp/assets/common/css/gitbucket.css index d84bc08..6fd7455 100644 --- a/src/main/webapp/assets/common/css/gitbucket.css +++ b/src/main/webapp/assets/common/css/gitbucket.css @@ -617,6 +617,30 @@ color: #888; } +#branch-control-title { + margin: 5px 10px; + font-weight: bold; +} + +#branch-control-close { + background: none; + border: none; + color: #aaa; + font-weight: bold; + line-height: 15px; +} + +#branch-control-input { + border: solid 1px #ccc; + margin: 10px; +} + +.new-branch-name { + font-weight: bold; + font-size: 1.2em; + padding-left: 16px; +} + /****************************************************************************/ /* nav pulls group */ /****************************************************************************/ @@ -877,6 +901,20 @@ background-color: white; } +ul.task-list { + padding-left: 2em; + margin-left: 0; +} + +li.task-list-item { + list-style-type: none; +} + +li.task-list-item input.task-list-item-checkbox { + margin: 0 4px 0.25em -20px; + vertical-align: middle; +} + /****************************************************************************/ /* Pull Request */ /****************************************************************************/ diff --git a/src/test/scala/view/GitBucketHtmlSerializerSpec.scala b/src/test/scala/view/GitBucketHtmlSerializerSpec.scala index 946b626..687db70 100644 --- a/src/test/scala/view/GitBucketHtmlSerializerSpec.scala +++ b/src/test/scala/view/GitBucketHtmlSerializerSpec.scala @@ -25,4 +25,69 @@ after mustEqual "foo%21bar%40baz%3e9000" } } + + "escapeTaskList" should { + "convert '- [ ] ' to '* task: :'" in { + val before = "- [ ] aaaa" + val after = escapeTaskList(before) + after mustEqual "* task: : aaaa" + } + + "convert ' - [ ] ' to ' * task: :'" in { + val before = " - [ ] aaaa" + val after = escapeTaskList(before) + after mustEqual " * task: : aaaa" + } + + "convert only first '- [ ] '" in { + val before = " - [ ] aaaa - [ ] bbb" + val after = escapeTaskList(before) + after mustEqual " * task: : aaaa - [ ] bbb" + } + + "convert '- [x] ' to '* task:x:'" in { + val before = " - [x] aaaa" + val after = escapeTaskList(before) + after mustEqual " * task:x: aaaa" + } + + "convert multi lines" in { + val before = """ +tasks +- [x] aaaa +- [ ] bbb +""" + val after = escapeTaskList(before) + after mustEqual """ +tasks +* task:x: aaaa +* task: : bbb +""" + } + + "no convert if inserted before '- [ ] '" in { + val before = " a - [ ] aaaa" + val after = escapeTaskList(before) + after mustEqual " a - [ ] aaaa" + } + + "no convert '- [] '" in { + val before = " - [] aaaa" + val after = escapeTaskList(before) + after mustEqual " - [] aaaa" + } + + "no convert '- [ ]a'" in { + val before = " - [ ]a aaaa" + val after = escapeTaskList(before) + after mustEqual " - [ ]a aaaa" + } + + "no convert '-[ ] '" in { + val before = " -[ ] aaaa" + val after = escapeTaskList(before) + after mustEqual " -[ ] aaaa" + } + } } +