mirror of
https://github.com/gitbucket/gitbucket.git
synced 2026-05-08 01:37:34 +02:00
Compare commits
51 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
54d1bff213 | ||
|
|
0075664b9a | ||
|
|
45f41a13e4 | ||
|
|
faae237ac5 | ||
|
|
e01758e74c | ||
|
|
db7dd31c79 | ||
|
|
7d7ac5e2be | ||
|
|
577016a33f | ||
|
|
fdf2102923 | ||
|
|
8b47e57be0 | ||
|
|
8dad6b64b0 | ||
|
|
05d36abdab | ||
|
|
04e31c5b4f | ||
|
|
6470428a85 | ||
|
|
a7efb3989a | ||
|
|
32072d0bbf | ||
|
|
78a3e4454d | ||
|
|
e576178e1e | ||
|
|
0f8bc2b03d | ||
|
|
43565458d4 | ||
|
|
71cc3be6d5 | ||
|
|
9a8eef7b19 | ||
|
|
a08c4368b7 | ||
|
|
3b456b2aab | ||
|
|
31559418ba | ||
|
|
692a6e43bc | ||
|
|
af4cce654c | ||
|
|
c63b02fd4a | ||
|
|
e6974b6e51 | ||
|
|
da3b7dbeff | ||
|
|
9737bd7012 | ||
|
|
55fd8e5e2d | ||
|
|
9c4f181d93 | ||
|
|
374342cfc1 | ||
|
|
6fbdd237d1 | ||
|
|
4e78f01a09 | ||
|
|
8853264808 | ||
|
|
6db9b8038f | ||
|
|
a7b48d63e4 | ||
|
|
3677906e95 | ||
|
|
5568acc5f3 | ||
|
|
c467594199 | ||
|
|
59c18056fc | ||
|
|
d8d18ed25c | ||
|
|
83fd2648f5 | ||
|
|
8e81758941 | ||
|
|
41a6a29771 | ||
|
|
3e0a50926f | ||
|
|
e408eb43bb | ||
|
|
dc0aa0851e | ||
|
|
51d7c43489 |
@@ -79,6 +79,13 @@ Run the following commands in `Terminal` to
|
||||
|
||||
Release Notes
|
||||
--------
|
||||
### 3.3 - 31 May 2015
|
||||
- Rich graphical diff for images
|
||||
- File finder is available in the repository viewer
|
||||
- Blame is displayed at the source viewer
|
||||
- Remain user data and repositories even if user is disabled
|
||||
- Mobile view improvement
|
||||
|
||||
### 3.2 - 3 May 2015
|
||||
- Directory history button
|
||||
- Compare / pull request button
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
#!/bin/sh
|
||||
./sbt.sh clean assembly
|
||||
|
||||
mvn deploy:deploy-file \
|
||||
-DgroupId=gitbucket\
|
||||
-DartifactId=gitbucket-assembly\
|
||||
-Dversion=3.1.1\
|
||||
-Dversion=3.2.0\
|
||||
-Dpackaging=jar\
|
||||
-Dfile=../target/scala-2.11/gitbucket-assembly-3.2.0.jar\
|
||||
-Dfile=../target/scala-2.11/gitbucket-assembly-3.3.0.jar\
|
||||
-DrepositoryId=sourceforge.jp\
|
||||
-Durl=scp://shell.sourceforge.jp/home/groups/a/am/amateras/htdocs/mvn/
|
||||
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 78 KiB |
@@ -8,3 +8,4 @@ Developer's Guide
|
||||
* [Activity Types](activity.md)
|
||||
* [Notification Email](notification.md)
|
||||
* [Automatic Schema Updating](auto_update.md)
|
||||
* [Release Operation](release.md)
|
||||
|
||||
68
doc/release.md
Normal file
68
doc/release.md
Normal file
@@ -0,0 +1,68 @@
|
||||
Release Operation
|
||||
========
|
||||
|
||||
Update version number
|
||||
--------
|
||||
|
||||
Note to update version number in files below:
|
||||
|
||||
### project/build.scala
|
||||
|
||||
```scala
|
||||
object MyBuild extends Build {
|
||||
val Organization = "gitbucket"
|
||||
val Name = "gitbucket"
|
||||
val Version = "3.2.0" // <---- update here!!
|
||||
val ScalaVersion = "2.11.6"
|
||||
val ScalatraVersion = "2.3.1"
|
||||
```
|
||||
|
||||
### src/main/scala/gitbucket/core/servlet/AutoUpdate.scala
|
||||
|
||||
```scala
|
||||
object AutoUpdate {
|
||||
|
||||
/**
|
||||
* The history of versions. A head of this sequence is the current BitBucket version.
|
||||
*/
|
||||
val versions = Seq(
|
||||
new Version(3, 2), // <---- add this!!
|
||||
new Version(3, 1),
|
||||
...
|
||||
```
|
||||
|
||||
### deploy-assembly/deploy-assembly-jar.sh
|
||||
|
||||
```bash
|
||||
#!/bin/sh
|
||||
./sbt.sh assembly
|
||||
|
||||
mvn deploy:deploy-file \
|
||||
-DgroupId=gitbucket\
|
||||
-DartifactId=gitbucket-assembly\
|
||||
-Dversion=3.2.0\ # <---- update here!!
|
||||
-Dpackaging=jar\
|
||||
-Dfile=../target/scala-2.11/gitbucket-assembly-x.x.x.jar\ # <---- update here!!
|
||||
-DrepositoryId=sourceforge.jp\
|
||||
-Durl=scp://shell.sourceforge.jp/home/groups/a/am/amateras/htdocs/mvn/
|
||||
```
|
||||
|
||||
Generate release files
|
||||
--------
|
||||
|
||||
Note: Release operation requires [Ant](http://ant.apache.org/) and [Maven](https://maven.apache.org/).
|
||||
|
||||
### Make release war file
|
||||
|
||||
Run ant with `build.xml` in the root directory. The release war file is generated into `target/scala-2.11/gitbucket.war`.
|
||||
|
||||
### Deploy assemnbly jar file
|
||||
|
||||
For plug-in development, we have to publish the assembly jar file to the public Maven repository.
|
||||
|
||||
```
|
||||
cd deploy-assembly/
|
||||
./deploy-assembly-jar.sh
|
||||
```
|
||||
|
||||
This script runs `sbt assembly` and `mvn deploy`.
|
||||
14
gitbucket-assembly.iml
Normal file
14
gitbucket-assembly.iml
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="false">
|
||||
<output url="file://$MODULE_DIR$/target/classes" />
|
||||
<output-test url="file://$MODULE_DIR$/target/test-classes" />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -10,7 +10,7 @@ import sbtassembly.AssemblyKeys._
|
||||
object MyBuild extends Build {
|
||||
val Organization = "gitbucket"
|
||||
val Name = "gitbucket"
|
||||
val Version = "3.2.0"
|
||||
val Version = "3.3.0"
|
||||
val ScalaVersion = "2.11.6"
|
||||
val ScalatraVersion = "2.3.1"
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
import gitbucket.core.model.IssueComment
|
||||
import gitbucket.core.util.RepositoryName
|
||||
|
||||
import java.util.Date
|
||||
|
||||
@@ -13,14 +14,16 @@ case class ApiComment(
|
||||
user: ApiUser,
|
||||
body: String,
|
||||
created_at: Date,
|
||||
updated_at: Date)
|
||||
updated_at: Date)(repositoryName: RepositoryName, issueId: Int){
|
||||
val html_url = ApiPath(s"/${repositoryName.fullName}/issues/${issueId}#comment-${id}")
|
||||
}
|
||||
|
||||
object ApiComment{
|
||||
def apply(comment: IssueComment, user: ApiUser): ApiComment =
|
||||
def apply(comment: IssueComment, repositoryName: RepositoryName, issueId: Int, user: ApiUser): ApiComment =
|
||||
ApiComment(
|
||||
id = comment.commentId,
|
||||
user = user,
|
||||
body = comment.content,
|
||||
created_at = comment.registeredDate,
|
||||
updated_at = comment.updatedDate)
|
||||
updated_at = comment.updatedDate)(repositoryName, issueId)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
import gitbucket.core.model.Issue
|
||||
import gitbucket.core.util.RepositoryName
|
||||
|
||||
import java.util.Date
|
||||
|
||||
@@ -16,10 +17,13 @@ case class ApiIssue(
|
||||
state: String,
|
||||
created_at: Date,
|
||||
updated_at: Date,
|
||||
body: String)
|
||||
body: String)(repositoryName: RepositoryName){
|
||||
val comments_url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/issues/${number}/comments")
|
||||
val html_url = ApiPath(s"/${repositoryName.fullName}/issues/${number}")
|
||||
}
|
||||
|
||||
object ApiIssue{
|
||||
def apply(issue: Issue, user: ApiUser): ApiIssue =
|
||||
def apply(issue: Issue, repositoryName: RepositoryName, user: ApiUser): ApiIssue =
|
||||
ApiIssue(
|
||||
number = issue.issueId,
|
||||
title = issue.title,
|
||||
@@ -27,5 +31,5 @@ object ApiIssue{
|
||||
state = if(issue.closed){ "closed" }else{ "open" },
|
||||
body = issue.content.getOrElse(""),
|
||||
created_at = issue.registeredDate,
|
||||
updated_at = issue.updatedDate)
|
||||
updated_at = issue.updatedDate)(repositoryName)
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ case class ApiUser(
|
||||
|
||||
object ApiUser{
|
||||
def apply(user: Account): ApiUser = ApiUser(
|
||||
login = user.fullName,
|
||||
login = user.userName,
|
||||
email = user.mailAddress,
|
||||
`type` = if(user.isGroupAccount){ "Organization" }else{ "User" },
|
||||
site_admin = user.isAdmin,
|
||||
|
||||
@@ -23,7 +23,7 @@ object JsonFormat {
|
||||
) + FieldSerializer[ApiUser]() + FieldSerializer[ApiPullRequest]() + FieldSerializer[ApiRepository]() +
|
||||
FieldSerializer[ApiCommitListItem.Parent]() + FieldSerializer[ApiCommitListItem]() + FieldSerializer[ApiCommitListItem.Commit]() +
|
||||
FieldSerializer[ApiCommitStatus]() + FieldSerializer[ApiCommit]() + FieldSerializer[ApiCombinedCommitStatus]() +
|
||||
FieldSerializer[ApiPullRequest.Commit]()
|
||||
FieldSerializer[ApiPullRequest.Commit]() + FieldSerializer[ApiIssue]() + FieldSerializer[ApiComment]()
|
||||
|
||||
|
||||
def apiPathSerializer(c: Context) = new CustomSerializer[ApiPath](format =>
|
||||
|
||||
@@ -202,15 +202,15 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
val userName = params("userName")
|
||||
|
||||
getAccountByUserName(userName, true).foreach { account =>
|
||||
// Remove repositories
|
||||
getRepositoryNamesOfUser(userName).foreach { repositoryName =>
|
||||
deleteRepository(userName, repositoryName)
|
||||
FileUtils.deleteDirectory(getRepositoryDir(userName, repositoryName))
|
||||
FileUtils.deleteDirectory(getWikiRepositoryDir(userName, repositoryName))
|
||||
FileUtils.deleteDirectory(getTemporaryDir(userName, repositoryName))
|
||||
}
|
||||
// Remove from GROUP_MEMBER, COLLABORATOR and REPOSITORY
|
||||
removeUserRelatedData(userName)
|
||||
// // Remove repositories
|
||||
// getRepositoryNamesOfUser(userName).foreach { repositoryName =>
|
||||
// deleteRepository(userName, repositoryName)
|
||||
// FileUtils.deleteDirectory(getRepositoryDir(userName, repositoryName))
|
||||
// FileUtils.deleteDirectory(getWikiRepositoryDir(userName, repositoryName))
|
||||
// FileUtils.deleteDirectory(getTemporaryDir(userName, repositoryName))
|
||||
// }
|
||||
// // Remove from GROUP_MEMBER, COLLABORATOR and REPOSITORY
|
||||
// removeUserRelatedData(userName)
|
||||
|
||||
updateAccount(account.copy(isRemoved = true))
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
issueId <- params("id").toIntOpt
|
||||
comments = getCommentsForApi(repository.owner, repository.name, issueId.toInt)
|
||||
} yield {
|
||||
JsonFormat(comments.map{ case (issueComment, user) => ApiComment(issueComment, ApiUser(user)) })
|
||||
JsonFormat(comments.map{ case (issueComment, user) => ApiComment(issueComment, RepositoryName(repository), issueId, ApiUser(user)) })
|
||||
}).getOrElse(NotFound)
|
||||
})
|
||||
|
||||
@@ -190,7 +190,7 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
(issue, id) <- handleComment(issueId, Some(body), repository)()
|
||||
issueComment <- getComment(repository.owner, repository.name, id.toString())
|
||||
} yield {
|
||||
JsonFormat(ApiComment(issueComment, ApiUser(context.loginAccount.get)))
|
||||
JsonFormat(ApiComment(issueComment, RepositoryName(repository), issueId, ApiUser(context.loginAccount.get)))
|
||||
}) getOrElse NotFound
|
||||
})
|
||||
|
||||
|
||||
@@ -32,7 +32,6 @@ class RepositoryViewerController extends RepositoryViewerControllerBase
|
||||
with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator with PullRequestService with CommitStatusService
|
||||
with WebHookPullRequestService
|
||||
|
||||
|
||||
/**
|
||||
* The repository viewer.
|
||||
*/
|
||||
@@ -284,27 +283,60 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
/**
|
||||
* Displays the file content of the specified branch or commit.
|
||||
*/
|
||||
get("/:owner/:repository/blob/*")(referrersOnly { repository =>
|
||||
val blobRoute = get("/:owner/:repository/blob/*")(referrersOnly { repository =>
|
||||
val (id, path) = splitPath(repository, multiParams("splat").head)
|
||||
val raw = params.get("raw").getOrElse("false").toBoolean
|
||||
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
||||
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id))
|
||||
val lastModifiedCommit = JGitUtil.getLastModifiedCommit(git, revCommit, path)
|
||||
getPathObjectId(git, path, revCommit).map { objectId =>
|
||||
if(raw){
|
||||
// Download
|
||||
JGitUtil.getContentFromId(git, objectId, true).map {bytes =>
|
||||
RawData(FileUtil.getContentType(path, bytes), bytes)
|
||||
JGitUtil.getContentFromId(git, objectId, true).map { bytes =>
|
||||
RawData("application/octet-stream", bytes)
|
||||
} getOrElse NotFound
|
||||
} else {
|
||||
html.blob(id, repository, path.split("/").toList, JGitUtil.getContentInfo(git, path, objectId),
|
||||
new JGitUtil.CommitInfo(lastModifiedCommit), hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
||||
html.blob(id, repository, path.split("/").toList,
|
||||
JGitUtil.getContentInfo(git, path, objectId),
|
||||
new JGitUtil.CommitInfo(JGitUtil.getLastModifiedCommit(git, revCommit, path)),
|
||||
hasWritePermission(repository.owner, repository.name, context.loginAccount),
|
||||
request.paths(2) == "blame")
|
||||
}
|
||||
} getOrElse NotFound
|
||||
}
|
||||
})
|
||||
|
||||
get("/:owner/:repository/blame/*"){
|
||||
blobRoute.action()
|
||||
}
|
||||
|
||||
/**
|
||||
* Blame data.
|
||||
*/
|
||||
ajaxGet("/:owner/:repository/get-blame/*")(referrersOnly { repository =>
|
||||
val (id, path) = splitPath(repository, multiParams("splat").head)
|
||||
contentType = formats("json")
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
||||
val last = git.log.add(git.getRepository.resolve(id)).addPath(path).setMaxCount(1).call.iterator.next.name
|
||||
Map(
|
||||
"root" -> s"${context.baseUrl}/${repository.owner}/${repository.name}",
|
||||
"id" -> id,
|
||||
"path" -> path,
|
||||
"last" -> last,
|
||||
"blame" -> JGitUtil.getBlame(git, id, path).map{ blame =>
|
||||
Map(
|
||||
"id" -> blame.id,
|
||||
"author" -> view.helpers.user(blame.authorName, blame.authorEmailAddress).toString,
|
||||
"avatar" -> view.helpers.avatarLink(blame.authorName, 32, blame.authorEmailAddress).toString,
|
||||
"authed" -> helper.html.datetimeago(blame.authorTime).toString,
|
||||
"prev" -> blame.prev,
|
||||
"prevPath" -> blame.prevPath,
|
||||
"commited" -> blame.commitTime.getTime,
|
||||
"message" -> blame.message,
|
||||
"lines" -> blame.lines)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Displays details of the specified commit.
|
||||
*/
|
||||
@@ -475,6 +507,34 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
repository)
|
||||
})
|
||||
|
||||
/**
|
||||
* Displays the file find of branch.
|
||||
*/
|
||||
get("/:owner/:repository/find/:ref")(referrersOnly { repository =>
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
||||
JGitUtil.getTreeId(git, params("ref")).map{ treeId =>
|
||||
html.find(params("ref"),
|
||||
treeId,
|
||||
repository,
|
||||
context.loginAccount match {
|
||||
case None => List()
|
||||
case account: Option[Account] => getGroupsByUserName(account.get.userName)
|
||||
})
|
||||
} getOrElse NotFound
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Get all file list of branch.
|
||||
*/
|
||||
ajaxGet("/:owner/:repository/tree-list/:tree")(referrersOnly { repository =>
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
||||
val treeId = params("tree")
|
||||
contentType = formats("json")
|
||||
Map("paths" -> JGitUtil.getAllFileListByTreeId(git, treeId))
|
||||
}
|
||||
})
|
||||
|
||||
private def splitPath(repository: RepositoryService.RepositoryInfo, path: String): (String, String) = {
|
||||
val id = repository.branchList.collectFirst {
|
||||
case branch if(path == branch || path.startsWith(branch + "/")) => branch
|
||||
|
||||
@@ -90,7 +90,7 @@ trait WebHookPullRequestService extends WebHookService {
|
||||
action = action,
|
||||
number = issue.issueId,
|
||||
repository = ApiRepository(repository, ApiUser(repoOwner)),
|
||||
issue = ApiIssue(issue, ApiUser(issueUser)),
|
||||
issue = ApiIssue(issue, RepositoryName(repository), ApiUser(issueUser)),
|
||||
sender = ApiUser(sender))
|
||||
}
|
||||
}
|
||||
@@ -272,8 +272,8 @@ object WebHookService {
|
||||
WebHookIssueCommentPayload(
|
||||
action = "created",
|
||||
repository = ApiRepository(repository, repositoryUser),
|
||||
issue = ApiIssue(issue, ApiUser(issueUser)),
|
||||
comment = ApiComment(comment, ApiUser(commentUser)),
|
||||
issue = ApiIssue(issue, RepositoryName(repository), ApiUser(issueUser)),
|
||||
comment = ApiComment(comment, RepositoryName(repository), issue.issueId, ApiUser(commentUser)),
|
||||
sender = ApiUser(sender))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ object AutoUpdate {
|
||||
* The history of versions. A head of this sequence is the current BitBucket version.
|
||||
*/
|
||||
val versions = Seq(
|
||||
new Version(3, 3),
|
||||
new Version(3, 2),
|
||||
new Version(3, 1),
|
||||
new Version(3, 0),
|
||||
|
||||
@@ -100,7 +100,8 @@ object JGitUtil {
|
||||
def isDifferentFromAuthor: Boolean = authorName != committerName || authorEmailAddress != committerEmailAddress
|
||||
}
|
||||
|
||||
case class DiffInfo(changeType: ChangeType, oldPath: String, newPath: String, oldContent: Option[String], newContent: Option[String])
|
||||
case class DiffInfo(changeType: ChangeType, oldPath: String, newPath: String, oldContent: Option[String], newContent: Option[String],
|
||||
oldIsImage: Boolean, newIsImage: Boolean, oldObjectId: Option[String], newObjectId: Option[String])
|
||||
|
||||
/**
|
||||
* The file content data for the file content view of the repository viewer.
|
||||
@@ -138,6 +139,9 @@ object JGitUtil {
|
||||
|
||||
case class BranchInfo(name: String, committerName: String, commitTime: Date, committerEmailAddress:String, mergeInfo: Option[BranchMergeInfo], commitId: String)
|
||||
|
||||
case class BlameInfo(id: String, authorName: String, authorEmailAddress: String, authorTime:java.util.Date,
|
||||
prev: Option[String], prevPath: Option[String], commitTime:java.util.Date, message:String, lines:Set[Int])
|
||||
|
||||
/**
|
||||
* Returns RevCommit from the commit or tag id.
|
||||
*
|
||||
@@ -324,6 +328,39 @@ object JGitUtil {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get all file list by revision. only file.
|
||||
*/
|
||||
def getTreeId(git: Git, revision: String): Option[String] = {
|
||||
using(new RevWalk(git.getRepository)){ revWalk =>
|
||||
val objectId = git.getRepository.resolve(revision)
|
||||
if(objectId==null) return None
|
||||
val revCommit = revWalk.parseCommit(objectId)
|
||||
Some(revCommit.getTree.name)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get all file list by tree object id.
|
||||
*/
|
||||
def getAllFileListByTreeId(git: Git, treeId: String): List[String] = {
|
||||
using(new RevWalk(git.getRepository)){ revWalk =>
|
||||
val objectId = git.getRepository.resolve(treeId+"^{tree}")
|
||||
if(objectId==null) return Nil
|
||||
using(new TreeWalk(git.getRepository)){ treeWalk =>
|
||||
treeWalk.addTree(objectId)
|
||||
treeWalk.setRecursive(true)
|
||||
var ret: List[String] = Nil
|
||||
if(treeWalk != null){
|
||||
while (treeWalk.next()) {
|
||||
ret +:= treeWalk.getPathString
|
||||
}
|
||||
}
|
||||
ret.reverse
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the commit list of the specified branch.
|
||||
*
|
||||
@@ -456,11 +493,13 @@ object JGitUtil {
|
||||
treeWalk.addTree(revCommit.getTree)
|
||||
val buffer = new scala.collection.mutable.ListBuffer[DiffInfo]()
|
||||
while(treeWalk.next){
|
||||
val newIsImage = FileUtil.isImage(treeWalk.getPathString)
|
||||
buffer.append((if(!fetchContent){
|
||||
DiffInfo(ChangeType.ADD, null, treeWalk.getPathString, None, None)
|
||||
DiffInfo(ChangeType.ADD, null, treeWalk.getPathString, None, None, false, newIsImage, None, Option(treeWalk.getObjectId(0)).map(_.name))
|
||||
} else {
|
||||
DiffInfo(ChangeType.ADD, null, treeWalk.getPathString, None,
|
||||
JGitUtil.getContentFromId(git, treeWalk.getObjectId(0), false).filter(FileUtil.isText).map(convertFromByteArray))
|
||||
JGitUtil.getContentFromId(git, treeWalk.getObjectId(0), false).filter(FileUtil.isText).map(convertFromByteArray),
|
||||
false, newIsImage, None, Option(treeWalk.getObjectId(0)).map(_.name))
|
||||
}))
|
||||
}
|
||||
(buffer.toList, None)
|
||||
@@ -480,12 +519,15 @@ object JGitUtil {
|
||||
import scala.collection.JavaConverters._
|
||||
git.getRepository.getConfig.setString("diff", null, "renames", "copies")
|
||||
git.diff.setNewTree(newTreeIter).setOldTree(oldTreeIter).call.asScala.map { diff =>
|
||||
if(!fetchContent || FileUtil.isImage(diff.getOldPath) || FileUtil.isImage(diff.getNewPath)){
|
||||
DiffInfo(diff.getChangeType, diff.getOldPath, diff.getNewPath, None, None)
|
||||
val oldIsImage = FileUtil.isImage(diff.getOldPath)
|
||||
val newIsImage = FileUtil.isImage(diff.getNewPath)
|
||||
if(!fetchContent || oldIsImage || newIsImage){
|
||||
DiffInfo(diff.getChangeType, diff.getOldPath, diff.getNewPath, None, None, oldIsImage, newIsImage, Option(diff.getOldId).map(_.name), Option(diff.getNewId).map(_.name))
|
||||
} else {
|
||||
DiffInfo(diff.getChangeType, diff.getOldPath, diff.getNewPath,
|
||||
JGitUtil.getContentFromId(git, diff.getOldId.toObjectId, false).filter(FileUtil.isText).map(convertFromByteArray),
|
||||
JGitUtil.getContentFromId(git, diff.getNewId.toObjectId, false).filter(FileUtil.isText).map(convertFromByteArray))
|
||||
JGitUtil.getContentFromId(git, diff.getNewId.toObjectId, false).filter(FileUtil.isText).map(convertFromByteArray),
|
||||
oldIsImage, newIsImage, Option(diff.getOldId).map(_.name), Option(diff.getNewId).map(_.name))
|
||||
}
|
||||
}.toList
|
||||
}
|
||||
@@ -639,21 +681,24 @@ object JGitUtil {
|
||||
|
||||
def getContentInfo(git: Git, path: String, objectId: ObjectId): ContentInfo = {
|
||||
// Viewer
|
||||
val large = FileUtil.isLarge(git.getRepository.getObjectDatabase.open(objectId).getSize)
|
||||
val viewer = if(FileUtil.isImage(path)) "image" else if(large) "large" else "other"
|
||||
val bytes = if(viewer == "other") JGitUtil.getContentFromId(git, objectId, false) else None
|
||||
using(git.getRepository.getObjectDatabase){ db =>
|
||||
val loader = db.open(objectId)
|
||||
val large = FileUtil.isLarge(loader.getSize)
|
||||
val viewer = if(FileUtil.isImage(path)) "image" else if(large) "large" else "other"
|
||||
val bytes = if(viewer == "other") JGitUtil.getContentFromId(git, objectId, false) else None
|
||||
|
||||
if(viewer == "other"){
|
||||
if(bytes.isDefined && FileUtil.isText(bytes.get)){
|
||||
// text
|
||||
ContentInfo("text", Some(StringUtil.convertFromByteArray(bytes.get)), Some(StringUtil.detectEncoding(bytes.get)))
|
||||
if(viewer == "other"){
|
||||
if(bytes.isDefined && FileUtil.isText(bytes.get)){
|
||||
// text
|
||||
ContentInfo("text", Some(StringUtil.convertFromByteArray(bytes.get)), Some(StringUtil.detectEncoding(bytes.get)))
|
||||
} else {
|
||||
// binary
|
||||
ContentInfo("binary", None, None)
|
||||
}
|
||||
} else {
|
||||
// binary
|
||||
ContentInfo("binary", None, None)
|
||||
// image or large
|
||||
ContentInfo(viewer, None, None)
|
||||
}
|
||||
} else {
|
||||
// image or large
|
||||
ContentInfo(viewer, None, None)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -666,12 +711,12 @@ object JGitUtil {
|
||||
* @return the byte array of content or None if object does not exist
|
||||
*/
|
||||
def getContentFromId(git: Git, id: ObjectId, fetchLargeFile: Boolean): Option[Array[Byte]] = try {
|
||||
val loader = git.getRepository.getObjectDatabase.open(id)
|
||||
if(fetchLargeFile == false && FileUtil.isLarge(loader.getSize)){
|
||||
None
|
||||
} else {
|
||||
using(git.getRepository.getObjectDatabase){ db =>
|
||||
Some(db.open(id).getBytes)
|
||||
using(git.getRepository.getObjectDatabase){ db =>
|
||||
val loader = db.open(id)
|
||||
if(fetchLargeFile == false && FileUtil.isLarge(loader.getSize)){
|
||||
None
|
||||
} else {
|
||||
Some(loader.getBytes)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
@@ -786,6 +831,36 @@ object JGitUtil {
|
||||
}
|
||||
}
|
||||
|
||||
def getBlame(git: Git, id: String, path: String): Iterable[BlameInfo] = {
|
||||
Option(git.getRepository.resolve(id)).map{ commitId =>
|
||||
val blamer = new org.eclipse.jgit.api.BlameCommand(git.getRepository);
|
||||
blamer.setStartCommit(commitId)
|
||||
blamer.setFilePath(path)
|
||||
val blame = blamer.call()
|
||||
var blameMap = Map[String, JGitUtil.BlameInfo]()
|
||||
var idLine = List[(String, Int)]()
|
||||
val commits = 0.to(blame.getResultContents().size()-1).map{ i =>
|
||||
val c = blame.getSourceCommit(i)
|
||||
if(!blameMap.contains(c.name)){
|
||||
blameMap += c.name -> JGitUtil.BlameInfo(
|
||||
c.name,
|
||||
c.getAuthorIdent.getName,
|
||||
c.getAuthorIdent.getEmailAddress,
|
||||
c.getAuthorIdent.getWhen,
|
||||
Option(git.log.add(c).addPath(blame.getSourcePath(i)).setSkip(1).setMaxCount(2).call.iterator.next)
|
||||
.map(_.name),
|
||||
if(blame.getSourcePath(i)==path){ None }else{ Some(blame.getSourcePath(i)) },
|
||||
c.getCommitterIdent.getWhen,
|
||||
c.getShortMessage,
|
||||
Set.empty)
|
||||
}
|
||||
idLine :+= (c.name, i)
|
||||
}
|
||||
val limeMap = idLine.groupBy(_._1).mapValues(_.map(_._2).toSet)
|
||||
blameMap.values.map{b => b.copy(lines=limeMap(b.id))}
|
||||
}.getOrElse(Seq.empty)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns sha1
|
||||
* @param owner repository owner
|
||||
|
||||
@@ -122,8 +122,8 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
|
||||
* Returns <img> which displays the avatar icon for the given user name.
|
||||
* This method looks up Gravatar if avatar icon has not been configured in user settings.
|
||||
*/
|
||||
def avatar(userName: String, size: Int, tooltip: Boolean = false)(implicit context: Context): Html =
|
||||
getAvatarImageHtml(userName, size, "", tooltip)
|
||||
def avatar(userName: String, size: Int, tooltip: Boolean = false, mailAddress: String = "")(implicit context: Context): Html =
|
||||
getAvatarImageHtml(userName, size, mailAddress, tooltip)
|
||||
|
||||
/**
|
||||
* Returns <img> which displays the avatar icon for the given mail address.
|
||||
@@ -203,7 +203,7 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
|
||||
* If user does not exist or disabled, this method returns avatar image without link.
|
||||
*/
|
||||
def avatarLink(userName: String, size: Int, mailAddress: String = "", tooltip: Boolean = false)(implicit context: Context): Html =
|
||||
userWithContent(userName, mailAddress)(avatar(userName, size, tooltip))
|
||||
userWithContent(userName, mailAddress)(avatar(userName, size, tooltip, mailAddress))
|
||||
|
||||
private def userWithContent(userName: String, mailAddress: String = "", styleClass: String = "")(content: Html)(implicit context: Context): Html =
|
||||
(if(mailAddress.isEmpty){
|
||||
|
||||
@@ -21,7 +21,7 @@ isCreateRepoOptionPublic: Boolean)(implicit context: gitbucket.core.controller.C
|
||||
<input type="hidden" name="owner" id="owner" value="@loginAccount.get.userName"/>
|
||||
</div>
|
||||
<span class="slash">/</span>
|
||||
<input type="text" name="name" id="name" />
|
||||
<input type="text" name="name" id="name" autofocus />
|
||||
<span id="error-name" class="error"></span>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<div class="span6">
|
||||
<fieldset>
|
||||
<label for="userName" class="strong">Username:</label>
|
||||
<input type="text" name="userName" id="userName" value=""/>
|
||||
<input type="text" name="userName" id="userName" value="" autofocus/>
|
||||
<span id="error-userName" class="error"></span>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
|
||||
@@ -90,13 +90,32 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 0;">
|
||||
@if(diff.newContent != None || diff.oldContent != None){
|
||||
@if(diff.oldObjectId == diff.newObjectId){
|
||||
<div class="diff-same">File renamed without changes</div>
|
||||
} else { @if(diff.newContent != None || diff.oldContent != None){
|
||||
<div id="diffText-@i" class="diffText"></div>
|
||||
<textarea id="newText-@i" style="display: none;" data-file-name="@diff.oldPath">@diff.newContent.getOrElse("")</textarea>
|
||||
<textarea id="oldText-@i" style="display: none;" data-file-name="@diff.newPath">@diff.oldContent.getOrElse("")</textarea>
|
||||
} else { @if(diff.newIsImage || diff.oldIsImage){
|
||||
<div class="diff-image-render diff2up">
|
||||
@if(oldCommitId.isDefined && diff.oldIsImage){
|
||||
<div class="diff-image-frame diff-old"><img src="@url(repository)/blob/@oldCommitId.get/@diff.oldPath?raw=true" class="diff-image" onload="onLoadedDiffImages(this)" style="display:none" /></div>
|
||||
} else {
|
||||
@if(diff.changeType != ChangeType.ADD){
|
||||
Not supported
|
||||
}
|
||||
}
|
||||
@if(newCommitId.isDefined && diff.newIsImage){
|
||||
<div class="diff-image-frame diff-new"><img src="@url(repository)/blob/@newCommitId.get/@diff.newPath?raw=true" class="diff-image" onload="onLoadedDiffImages(this)" style="display:none" /></div>
|
||||
} else {
|
||||
@if(diff.changeType != ChangeType.DELETE){
|
||||
Not supported
|
||||
}
|
||||
}
|
||||
</div>
|
||||
} else {
|
||||
Not supported
|
||||
}
|
||||
} } }
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
@user(issue.get.openedUserName, styleClass="username strong") <span class="muted">commented @helper.html.datetimeago(issue.get.registeredDate)</span>
|
||||
<span class="pull-right">
|
||||
@if(hasWritePermission || loginAccount.map(_.userName == issue.get.openedUserName).getOrElse(false)){
|
||||
<a href="#" data-issue-id="@issue.get.issueId"><i class="icon-pencil"></i></a>
|
||||
<a href="#" data-issue-id="@issue.get.issueId"><i class="icon-pencil" aria-label="Edit"></i></a>
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
@@ -41,8 +41,8 @@
|
||||
<span class="pull-right">
|
||||
@if(comment.action != "commit" && comment.action != "merge" && comment.action != "refer"
|
||||
&& (hasWritePermission || loginAccount.map(_.userName == comment.commentedUserName).getOrElse(false))){
|
||||
<a href="#" data-comment-id="@comment.commentId"><i class="icon-pencil"></i></a>
|
||||
<a href="#" data-comment-id="@comment.commentId"><i class="icon-remove-circle"></i></a>
|
||||
<a href="#" data-comment-id="@comment.commentId"><i class="icon-pencil" aria-label="Edit"></i></a>
|
||||
<a href="#" data-comment-id="@comment.commentId"><i class="icon-remove-circle" aria-label="Remove"></i></a>
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<div class="box issue-box">
|
||||
<div class="box-content">
|
||||
<span id="error-title" class="error"></span>
|
||||
<input type="text" name="title" value="" placeholder="Title" style="width: 565px;"/>
|
||||
<input type="text" name="title" value="" placeholder="Title" style="width: 565px;" autofocus/>
|
||||
<div>
|
||||
<span id="label-assigned">No one is assigned</span>
|
||||
@if(hasWritePermission){
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>@title</title>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<link rel="icon" href="@assets/common/images/gitbucket.png" type="image/vnd.microsoft.icon" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link href="@assets/vendors/bootstrap/css/bootstrap.css" rel="stylesheet">
|
||||
@@ -17,7 +18,7 @@
|
||||
<link href="@assets/vendors/google-code-prettify/prettify.css" type="text/css" rel="stylesheet"/>
|
||||
<link href="@assets/vendors/facebox/facebox.css" rel="stylesheet"/>
|
||||
<link href="@assets/common/css/gitbucket.css" rel="stylesheet">
|
||||
<script src="@assets/vendors/jquery/jquery-1.9.1.js"></script>
|
||||
<script src="@assets/vendors/jquery/jquery-1.11.1.js"></script>
|
||||
<script src="@assets/vendors/dropzone/dropzone.js"></script>
|
||||
<script src="@assets/common/js/validation.js"></script>
|
||||
<script src="@assets/common/js/gitbucket.js"></script>
|
||||
@@ -28,6 +29,7 @@
|
||||
<script src="@assets/vendors/zclip/ZeroClipboard.min.js"></script>
|
||||
<script src="@assets/vendors/elastic/jquery.elastic.source.js"></script>
|
||||
<script src="@assets/vendors/facebox/facebox.js"></script>
|
||||
<script src="@assets/vendors/jquery-hotkeys/jquery.hotkeys.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<form id="search" action="@path/search" method="POST">
|
||||
|
||||
@@ -3,12 +3,32 @@
|
||||
pathList: List[String],
|
||||
content: gitbucket.core.util.JGitUtil.ContentInfo,
|
||||
latestCommit: gitbucket.core.util.JGitUtil.CommitInfo,
|
||||
hasWritePermission: Boolean)(implicit context: gitbucket.core.controller.Context)
|
||||
hasWritePermission: Boolean,
|
||||
isBlame: Boolean)(implicit context: gitbucket.core.controller.Context)
|
||||
@import context._
|
||||
@import gitbucket.core.view.helpers._
|
||||
@html.main(s"${repository.owner}/${repository.name}", Some(repository)) {
|
||||
@html.menu("code", repository){
|
||||
<div class="head">
|
||||
<div class="pull-right hide-if-blame"><div class="btn-group">
|
||||
<a href="@url(repository)/find/@encodeRefName(branch)" class="btn btn-mini" data-toggle="tooltip" data-placement="bottom" data-hotkey="t" title="Quickly jump between files"><i class="icon icon-th-list"></i></a>
|
||||
</div></div>
|
||||
<div class="line-age-legend">
|
||||
<span>Newer</span>
|
||||
<ol>
|
||||
<li class="heat1"></li>
|
||||
<li class="heat2"></li>
|
||||
<li class="heat3"></li>
|
||||
<li class="heat4"></li>
|
||||
<li class="heat5"></li>
|
||||
<li class="heat6"></li>
|
||||
<li class="heat7"></li>
|
||||
<li class="heat8"></li>
|
||||
<li class="heat9"></li>
|
||||
<li class="heat10"></li>
|
||||
</ol>
|
||||
<span>Older</span>
|
||||
</div>
|
||||
@helper.html.branchcontrol(
|
||||
branch,
|
||||
repository,
|
||||
@@ -42,6 +62,9 @@
|
||||
<a class="btn btn-mini" href="@url(repository)/edit/@encodeRefName(branch)/@pathList.mkString("/")">Edit</a>
|
||||
}
|
||||
<a class="btn btn-mini" href="?raw=true">Raw</a>
|
||||
@if(content.viewType == "text"){
|
||||
<a class="btn btn-mini blame-action" href="@url(repository)/blame/@latestCommit.id/@pathList.mkString("/")" data-url="@url(repository)/get-blame/@latestCommit.id/@pathList.mkString("/")" data-repository="@url(repository)">Blame</a>
|
||||
}
|
||||
<a class="btn btn-mini" href="@url(repository)/commits/@encodeRefName(branch)/@pathList.mkString("/")">History</a>
|
||||
@if(hasWritePermission){
|
||||
<a class="btn btn-mini btn-danger" href="@url(repository)/remove/@encodeRefName(branch)/@pathList.mkString("/")">Delete</a>
|
||||
@@ -52,13 +75,13 @@
|
||||
<tr>
|
||||
<td>
|
||||
@if(content.viewType == "text"){
|
||||
@defining(pathList.reverse.head) { file =>
|
||||
@if(renderableSuffixes.find(suffix => file.toLowerCase.endsWith(suffix))) {
|
||||
@defining(renderableSuffixes.find(suffix => pathList.reverse.head.toLowerCase.endsWith(suffix))) { isRrenderable =>
|
||||
@if(!isBlame && isRrenderable) {
|
||||
<div class="box-content markdown-body" style="border: none; padding-left: 16px; padding-right: 16px;">
|
||||
@renderMarkup(pathList, content.content.get, branch, repository, false, false)
|
||||
</div>
|
||||
} else {
|
||||
<pre class="prettyprint linenums blob">@content.content.get</pre>
|
||||
<pre class="prettyprint linenums blob @if(!isRrenderable){ no-renderable } ">@content.content.get</pre>
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -84,30 +107,111 @@ $(window).load(function(){
|
||||
updateHighlighting();
|
||||
}).hashchange();
|
||||
|
||||
$('pre.prettyprint ol.linenums li').each(function(i, e){
|
||||
var pre = $('pre.prettyprint');
|
||||
pre.append($('<div class="source-line-num">')
|
||||
.data('line', (i + 1))
|
||||
.css({
|
||||
cursor : 'pointer',
|
||||
var pre = $('pre.prettyprint');
|
||||
function updateSourceLineNum(){
|
||||
$('.source-line-num').remove();
|
||||
var pos = pre.find('ol.linenums').position();
|
||||
$('<div class="source-line-num">').css({
|
||||
height:pre.height(),
|
||||
width:'48px',
|
||||
cursor:'pointer',
|
||||
position: 'absolute',
|
||||
top : $(e).position().top + 'px',
|
||||
left : pre.position().left + 'px',
|
||||
width : ($(e).position().left - pre.position().left) + 'px',
|
||||
height : '16px'
|
||||
}));
|
||||
});
|
||||
|
||||
$('div.source-line-num').click(function(e){
|
||||
var line = $(e.target).data('line');
|
||||
var hash = location.hash;
|
||||
if(e.shiftKey == true && hash.match(/#L\d+(-L\d+)?/)){
|
||||
var lines = hash.split('-');
|
||||
location.hash = lines[0] + '-L' + line;
|
||||
} else {
|
||||
location.hash = '#L' + line;
|
||||
top : pos.top + 'px',
|
||||
left : pos.left + 'px'
|
||||
}).click(function(e){
|
||||
$(window).hashchange(function(){})
|
||||
var pos = $(this).data("pos");
|
||||
if(!pos){
|
||||
pos = $('ol.linenums li').map(function(){ return {id:$(this).attr("id"),top:$(this).position().top} }).toArray();
|
||||
$(this).data("pos",pos);
|
||||
}
|
||||
for(var i=0;i<pos.length-1;i++){
|
||||
if(pos[i+1].top>e.pageY){
|
||||
break;
|
||||
}
|
||||
}
|
||||
var line = pos[i].id.replace(/^L/,'');
|
||||
var hash = location.hash;
|
||||
if(e.shiftKey == true && hash.match(/#L\d+(-L\d+)?/)){
|
||||
var lines = hash.split('-');
|
||||
location.hash = lines[0] + '-L' + line;
|
||||
} else {
|
||||
var p = $("#L"+line).attr('id',"");
|
||||
location.hash = '#L' + line;
|
||||
p.attr('id','L'+line);
|
||||
}
|
||||
}).appendTo(pre);
|
||||
}
|
||||
var repository = $('.blame-action').data('repository');
|
||||
$('.blame-action').click(function(e){
|
||||
if(history.pushState && $('pre.prettyprint.no-renderable').length){
|
||||
e.preventDefault();
|
||||
history.pushState(null, null, this.href);
|
||||
updateBlame();
|
||||
}
|
||||
});
|
||||
|
||||
function updateBlame(){
|
||||
var m = /^\/(blame|blob)(\/.*)$/.exec(location.pathname.substring(repository.length));
|
||||
var mode = m[1];
|
||||
$('.blame-action').toggleClass("active", mode=='blame').attr('href', repository + (m[1]=='blame'?'/blob':'/blame')+m[2]);
|
||||
if(pre.parents("td").find(".blame").length){
|
||||
pre.parents("div.container").toggleClass("blame-container", mode=='blame');
|
||||
updateSourceLineNum();
|
||||
return;
|
||||
}
|
||||
if(mode=='blob'){
|
||||
updateSourceLineNum();
|
||||
return;
|
||||
}
|
||||
$(document.body).toggleClass('no-box-shadow',document.body.style.boxShadow===undefined);
|
||||
$('.blame-action').addClass("active");
|
||||
var base = $('<div class="blame">').css({height:pre.height()}).prependTo(pre.parents("td")[0]);
|
||||
base.parents("div.container").addClass("blame-container");
|
||||
updateSourceLineNum();
|
||||
$.get($('.blame-action').data('url')).done(function(data){
|
||||
var blame = data.blame;
|
||||
var index = [];
|
||||
for(var i=0;i<blame.length;i++){
|
||||
for(var j=0;j<blame[i].lines.length;j++){
|
||||
index[blame[i].lines[j]]=blame[i];
|
||||
}
|
||||
}
|
||||
var blame, lastDiv, now=new Date().getTime();
|
||||
|
||||
$('pre.prettyprint ol.linenums li').each(function(i, e){
|
||||
var p=$(e).position();
|
||||
var h=$(e).height();
|
||||
if(blame == index[i]){
|
||||
lastDiv.css("min-height",(p.top+h+1) - lastDiv.position().top);
|
||||
}else{
|
||||
$(e).addClass('blame-sep')
|
||||
blame = index[i];
|
||||
var sha = $('<div class="blame-sha">')
|
||||
.append($('<a>').attr("href",data.root+'/commit/'+blame.id).text(blame.id.substr(0,7)));
|
||||
if(blame.prev){
|
||||
sha.append($('<br />'))
|
||||
.append($('<a class="muted-link">').text('prev').attr("href",data.root+'/blame/'+blame.prev+'/'+(blame.prevPath||data.path)));
|
||||
}
|
||||
lastDiv = $('<div class="blame-info">')
|
||||
.addClass('heat'+Math.min(10,Math.max(1,Math.ceil((now-blame.commited)/(24*3600*1000*70)))))
|
||||
.toggleClass('blame-last',blame.id==data.last)
|
||||
.data('line', (i + 1))
|
||||
.css({
|
||||
"top" : p.top + 'px',
|
||||
"min-height" : h+'px'
|
||||
})
|
||||
.append(sha)
|
||||
.append($(blame.avatar).addClass('avatar').css({"float":"left"}))
|
||||
.append($('<div class="blame-commit-title">').text(blame.message))
|
||||
.append($('<div class="muted">').html(blame.author+ " authed "+blame.authed))
|
||||
.appendTo(base);
|
||||
}
|
||||
});
|
||||
});
|
||||
return false;
|
||||
};
|
||||
updateBlame();
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -120,15 +224,20 @@ function updateHighlighting(){
|
||||
var lines = hash.substr(1).split('-');
|
||||
if(lines.length == 1){
|
||||
$('#' + lines[0]).addClass('highlight');
|
||||
$(window).scrollTop($('#' + lines[0]).offset().top - 40);
|
||||
if(!updateHighlighting.scrolling){
|
||||
$(window).scrollTop($('#' + lines[0]).offset().top - 40);
|
||||
}
|
||||
} else if(lines.length > 1){
|
||||
var start = parseInt(lines[0].substr(1));
|
||||
var end = parseInt(lines[1].substr(1));
|
||||
for(var i = start; i <= end; i++){
|
||||
$('#L' + i).addClass('highlight');
|
||||
}
|
||||
$(window).scrollTop($('#L' + start).offset().top - 40);
|
||||
if(!updateHighlighting.scrolling){
|
||||
$(window).scrollTop($('#L' + start).offset().top - 40);
|
||||
}
|
||||
}
|
||||
updateHighlighting.scrolling = true;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -14,11 +14,12 @@
|
||||
@html.main(s"${repository.owner}/${repository.name}", Some(repository)) {
|
||||
@html.menu("code", repository, Some(branch), pathList.isEmpty, groupNames.isEmpty, info, error){
|
||||
<div class="head">
|
||||
<div class="pull-right">
|
||||
<div class="pull-right"><div class="btn-group">
|
||||
<a href="@url(repository)/find/@encodeRefName(branch)" class="btn btn-mini" data-toggle="tooltip" data-placement="bottom" data-hotkey="t" title="Quickly jump between files"><i class="icon icon-th-list"></i></a>
|
||||
@if(pathList.nonEmpty){
|
||||
<a href="@url(repository)/commits/@encodeRefName(branch)/@pathList.mkString("/")" class="btn btn-mini" data-toggle="tooltip" data-placement="bottom" title="Browse commits for this branch"><i class="icon icon-time"></i></a>
|
||||
}
|
||||
</div>
|
||||
</div></div>
|
||||
@branchPullRequest.map{ case (pullRequest, issue) =>
|
||||
<a href="@url(repository)/pull/@pullRequest.issueId" class="btn btn-pullrequest-branch btn-mini" title="@issue.title" data-toggle="tooltip">#@pullRequest.issueId</a>
|
||||
}.getOrElse{
|
||||
|
||||
121
src/main/twirl/gitbucket/core/repo/find.scala.html
Normal file
121
src/main/twirl/gitbucket/core/repo/find.scala.html
Normal file
@@ -0,0 +1,121 @@
|
||||
@(branch: String,
|
||||
treeId: String,
|
||||
repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
|
||||
groupNames: List[String]
|
||||
)(implicit context: gitbucket.core.controller.Context)
|
||||
@import context._
|
||||
@import gitbucket.core.view.helpers._
|
||||
@html.main(s"${repository.owner}/${repository.name}", Some(repository)) {
|
||||
@html.menu("code", repository, Some(branch), false, groupNames.isEmpty){
|
||||
|
||||
<div>
|
||||
<div class="find-input">
|
||||
<span class="bold"><a href="@url(repository)/tree/@encodeRefName(branch)">@repository.name</a></span>
|
||||
/
|
||||
<input type="text" name="query" autocomplete="off" spellcheck="false" autofocus id="tree-finder-field" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-info">
|
||||
<button type="button" class="close" data-dismiss="alert">×</button>
|
||||
You've activated the <em>file finder</em>
|
||||
by pressing <code>t</code>.
|
||||
Start typing to filter the file list. Use <code>↑</code> and
|
||||
<code>↓</code> to navigate,
|
||||
<code>enter</code> to view files.
|
||||
</div>
|
||||
<table id="tree-finder-results" class="table table-file-list" data-url="@url(repository)/tree-list/@treeId">
|
||||
<tbody class="tree-browser-result-template">
|
||||
<tr class="tree-browser-result">
|
||||
<td class="icon"><span class="icon icon-chevron-right"></span></td>
|
||||
<td class="icon"><img src="@assets/common/images/file.png"/></td>
|
||||
<td>
|
||||
<a href="@url(repository)/blob/@encodeRefName(branch)"></a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tbody class="no-results" style="display:none">
|
||||
<tr><th colspan="3">No matching files</th><tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<script>
|
||||
$(function(){
|
||||
var paths = [];
|
||||
var template = $('.tree-browser-result-template tr').clone();
|
||||
var res = $('.tree-browser-result-template');
|
||||
var cursor = 0;
|
||||
var pathBase = template.find("a").attr("href");
|
||||
var preKeyword;
|
||||
$.ajax({
|
||||
url:$('#tree-finder-results').data('url'),
|
||||
cache: true,
|
||||
dataType: 'json',
|
||||
success:function(data){
|
||||
paths = data.paths;
|
||||
filter();
|
||||
}
|
||||
});
|
||||
var timer;
|
||||
$("#tree-finder-field").keydown(function(e){
|
||||
var target = $(this);
|
||||
if(e.keyCode == 40){ // DOWN
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
changeCursor(cursor+1);
|
||||
}else if(e.keyCode==38){ // UP
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
changeCursor(cursor-1);
|
||||
}else if(e.keyCode==13){ // ENTER
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
target = $(".tree-browser-result.navigation-focus a");
|
||||
if(target[0]){
|
||||
target[0].click();
|
||||
}
|
||||
}else if(e.keyCode==27){ // ESC
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
history.back();
|
||||
}else{
|
||||
clearTimeout(timer);
|
||||
timer=setTimeout(filter,300);
|
||||
}
|
||||
});
|
||||
function changeCursor(newPos){
|
||||
if(!$(".tree-browser-result")[newPos]){
|
||||
return $(".tree-browser-result.navigation-focus");
|
||||
}
|
||||
$(".tree-browser-result.navigation-focus").removeClass("navigation-focus");
|
||||
cursor=newPos;
|
||||
scrollIntoView($($(".tree-browser-result")[cursor]).addClass("navigation-focus"));
|
||||
}
|
||||
function filter(){
|
||||
var v = $('#tree-finder-field').val();
|
||||
if(v==preKeyword || paths.length==0){
|
||||
return;
|
||||
}
|
||||
scrollIntoView('#tree-finder-field');
|
||||
preKeyword=v;
|
||||
cursor=0;
|
||||
var p = string_score_sort(v, paths, 50);
|
||||
res.html("");
|
||||
if(p.length==0){
|
||||
$(".no-results").show();
|
||||
return;
|
||||
}else{
|
||||
$(".no-results").hide();
|
||||
for(var i=0;i < p.length;i++){
|
||||
var row = template.clone();
|
||||
row.find("a").attr("href",pathBase+"/"+p[i].string).html(string_score_highlight(p[i], '<b>'));
|
||||
if(cursor==i){
|
||||
row.addClass("navigation-focus");
|
||||
}
|
||||
row.appendTo(res);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@
|
||||
<form action="@path/signin" method="POST" validate="true">
|
||||
<label for="userName">Username:</label>
|
||||
<span id="error-userName" class="error"></span>
|
||||
<input type="text" name="userName" id="userName" style="width: 95%"/>
|
||||
<input type="text" name="userName" id="userName" style="width: 95%" autofocus/>
|
||||
<label for="password">Password:</label>
|
||||
<span id="error-password" class="error"></span>
|
||||
<input type="password" name="password" id="password" style="width: 95%"/>
|
||||
|
||||
@@ -1151,6 +1151,149 @@ table.diff tbody tr.not-diff:hover td{
|
||||
white-space: nowrap;
|
||||
letter-spacing: 0px;
|
||||
}
|
||||
.diff-same{
|
||||
background: #DDD;
|
||||
color: #BBB;
|
||||
font-size: 16px;
|
||||
padding: 20px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
/* ------- for imageDiff */
|
||||
.diff-image-frame{
|
||||
display: table-cell;
|
||||
*float: left; /* for ie7 */
|
||||
vertical-align: middle;
|
||||
padding: 20px;
|
||||
background-color: #eee;
|
||||
}
|
||||
.diff-image-frame.diff-old{
|
||||
padding-right: 2px;
|
||||
}
|
||||
.diff-image-frame.diff-new{
|
||||
padding-left: 2px;
|
||||
}
|
||||
.diff-image-frame .diff-meta{
|
||||
margin-top: 12px;
|
||||
color: #999;
|
||||
font-family: Helvetica,arial,freesans,clean,sans-serif;
|
||||
font-size: 12px;
|
||||
}
|
||||
.diff-image-frame.diff-old .diff-meta .diff{
|
||||
color: #bd2c00;
|
||||
}
|
||||
.diff-image-frame.diff-new .diff-meta .diff{
|
||||
color: #55a532;
|
||||
}
|
||||
.diff-image-frame img{
|
||||
max-height: 410px;
|
||||
max-width: 410px;
|
||||
background: url(../images/checker.png);
|
||||
}
|
||||
.diff-image-render.diff2up{
|
||||
width:100%;
|
||||
text-align: center;
|
||||
display: table;
|
||||
}
|
||||
.diff-image-frame.diff-new img{
|
||||
border: 1px solid #55a532;
|
||||
}
|
||||
.diff-image-frame.diff-old img{
|
||||
border: 1px solid #bd2c00;
|
||||
}
|
||||
.diff-image-stack{
|
||||
position: relative;
|
||||
background: #EEE;
|
||||
padding-top: 20px;
|
||||
}
|
||||
.diff-image-stack .diff-old,
|
||||
.diff-image-stack .diff-new{
|
||||
position:absolute;
|
||||
overflow: hidden;
|
||||
margin:0 20px;
|
||||
}
|
||||
.diff-image-stack img {
|
||||
max-width: none;
|
||||
background: url(../images/checker.png);
|
||||
}
|
||||
.diff-image-stack .diff-new{
|
||||
border: 1px solid #55a532;
|
||||
background: #EEE;
|
||||
}
|
||||
.diff-image-stack .diff-old{
|
||||
border: 1px solid #bd2c00;
|
||||
}
|
||||
.diff-swipe-handle{
|
||||
position:absolute;
|
||||
margin-left: 325px;
|
||||
left: 100px;
|
||||
}
|
||||
.diff-silde-bar{
|
||||
width: 200px;
|
||||
position: absolute;
|
||||
left: 325px;
|
||||
margin: 6px 0 0 7px;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid gray;
|
||||
height: 8px;
|
||||
}
|
||||
.image-diff-tools{
|
||||
text-align: center;
|
||||
padding: 4px;
|
||||
background: #f7f7f7;
|
||||
}
|
||||
.image-diff-tools{
|
||||
font-family: 'Helvetica Neue', Helvetica, arial, freesans, clean, sans-serif;
|
||||
font-size: 12px;
|
||||
background: #f7f7f7;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
}
|
||||
.image-diff-tools li{
|
||||
background: none;
|
||||
display: inline;
|
||||
cursor: pointer;
|
||||
border-right: 1px solid #ccc;
|
||||
padding: 0 5px;
|
||||
position: relative;
|
||||
color: #666;
|
||||
}
|
||||
.image-diff-tools li:last-child{
|
||||
border-right: none;
|
||||
}
|
||||
.image-diff-tools li.active {
|
||||
font-weight: bold;
|
||||
}
|
||||
.no-canvas .need-canvas{
|
||||
display: none;
|
||||
}
|
||||
.diff-image-stack.swipe .diff-new{
|
||||
border-right: 1px solid #888;
|
||||
}
|
||||
.diff-image-stack.swipe .diff-swipe-handle{
|
||||
margin-left: 15px;
|
||||
left: 410px;
|
||||
}
|
||||
.diff-image-stack.swipe .diff-silde-bar{
|
||||
display: none;
|
||||
}
|
||||
|
||||
.diff-image-stack.onion .diff-silde-bar{
|
||||
background: -ms-linear-gradient(left, #bd2c00 0%,#55a532 100%); /* IE10+ */
|
||||
background: linear-gradient(to right, #bd2c00 0%,#55a532 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#bd2c00', endColorstr='#55a532',GradientType=1 ); /* IE6-9 */
|
||||
}
|
||||
|
||||
.diff-image-stack.blink .diff-silde-bar{
|
||||
border-style: dotted;
|
||||
background-image: linear-gradient(to right, #bd2c00, #bd2c00 50%, #55a532 50%, #55a532 100%);
|
||||
background-size: 2px 2px;
|
||||
}
|
||||
|
||||
.diff-image-stack.difference {
|
||||
padding-bottom: 18px;
|
||||
text-align: center;
|
||||
}
|
||||
/****************************************************************************/
|
||||
/* Repository Settings */
|
||||
/****************************************************************************/
|
||||
@@ -1158,7 +1301,7 @@ ul.collaborator {
|
||||
list-style-type: none;
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
||||
|
||||
ul.collaborator li {
|
||||
background-color: #eee;
|
||||
border: 1px solid #ccc;
|
||||
@@ -1166,7 +1309,7 @@ ul.collaborator li {
|
||||
padding: 6px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
|
||||
ul.collaborator li:hover {
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
@@ -1225,6 +1368,8 @@ div.markdown-body pre {
|
||||
font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
|
||||
font-size: 12px;
|
||||
white-space: pre;
|
||||
word-wrap: normal;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
div.markdown-body code {
|
||||
@@ -1405,3 +1550,227 @@ h5 a.markdown-anchor-link {
|
||||
h6 a.markdown-anchor-link {
|
||||
top: 6px;
|
||||
}
|
||||
|
||||
/****************************************************************************/
|
||||
/* File finder */
|
||||
/****************************************************************************/
|
||||
#tree-finder-field{
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
vertical-align: baseline;
|
||||
font-size: 100%;
|
||||
height: inherit;
|
||||
width: 780px;
|
||||
}
|
||||
.find-input{
|
||||
font-size: 18px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
#tree-finder-results td{
|
||||
padding:7px 6px;
|
||||
}
|
||||
#tree-finder-results td.icon{
|
||||
width:16px; padding: 7px 2px 7px 6px;
|
||||
}
|
||||
#tree-finder-results .tree-browser-result .icon-chevron-right{
|
||||
visibility: hidden;
|
||||
}
|
||||
#tree-finder-results .tree-browser-result.navigation-focus .icon-chevron-right{
|
||||
visibility: visible;
|
||||
}
|
||||
#tree-finder-results .navigation-focus td{
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
/****************************************************************************/
|
||||
/* blame */
|
||||
/****************************************************************************/
|
||||
.blobview pre.blob{
|
||||
padding-left: 0;
|
||||
}
|
||||
.blobview ol.linenums{
|
||||
margin-left: 0;
|
||||
padding-left: 50px;
|
||||
}
|
||||
|
||||
div.container.blame-container{
|
||||
width:1270px;
|
||||
}
|
||||
.line-age-legend {
|
||||
display: none;
|
||||
}
|
||||
.blame-container .line-age-legend {
|
||||
display: block;
|
||||
float: right;
|
||||
font-size: 12px;
|
||||
color: #777;
|
||||
}
|
||||
.blame-container .line-age-legend ol {
|
||||
display: inline-block;
|
||||
*display: inline;
|
||||
*zoom: 1;
|
||||
list-style: none;
|
||||
margin: 0 5px;
|
||||
}
|
||||
.blame-container .line-age-legend ol li {
|
||||
display: inline-block;
|
||||
*display: inline;
|
||||
*zoom: 1;
|
||||
width: 8px;
|
||||
height: 10px;
|
||||
}
|
||||
.blame-container pre.blob{
|
||||
margin-left: 350px;
|
||||
}
|
||||
.blame-container pre.prettyprint ol.linenums li.blame-sep{
|
||||
border-top: 1px solid rgb(219, 219, 219);
|
||||
margin-top: -1px;
|
||||
}
|
||||
.blame-container .hide-if-blame {
|
||||
display: none;
|
||||
}
|
||||
.blame{
|
||||
font-size: 12px;
|
||||
white-space: normal;
|
||||
width: 340px;
|
||||
float: left;
|
||||
min-height: 100px;
|
||||
display: none;
|
||||
}
|
||||
.blame-container .blame{
|
||||
display: block;
|
||||
}
|
||||
.blame .blame-commit-title{
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
line-height: 1.1;
|
||||
}
|
||||
.blame .avatar{
|
||||
margin-right: 4px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.blame .blame-info{
|
||||
background: white;
|
||||
box-shadow:rgba(113, 135, 164, 0.65098) 0px 0px 4px 0px;
|
||||
position: absolute;
|
||||
width: 340px;
|
||||
padding: 2px;
|
||||
border-right: 2px solid;
|
||||
}
|
||||
.no-box-shadow .blame .blame-info{
|
||||
border-top: 1px solid #888;
|
||||
border-bottom: 1px solid #888;
|
||||
border-left: 1px solid #888;
|
||||
}
|
||||
.blame-sha{
|
||||
font: 11px Consolas, "Liberation Mono", Menlo, Courier, monospace;
|
||||
float: right;
|
||||
text-align: right;
|
||||
}
|
||||
.blame-sha .muted-link{
|
||||
color: #777;
|
||||
}
|
||||
.blame-sha .muted-link:hover{
|
||||
color: #4183c4;
|
||||
}
|
||||
|
||||
.blame .blame-info:hover{
|
||||
z-index: 100;
|
||||
box-shadow:rgba(113, 135, 164, 0.65098) 0px 0px 4px 3px;
|
||||
}
|
||||
.blame .blame-info.blame-last{
|
||||
background: #FDFCED;
|
||||
}
|
||||
.blame-info.heat1{ border-right-color:#ffeca7}
|
||||
.blame-info.heat2{ border-right-color:#ffdd8c}
|
||||
.blame-info.heat3{ border-right-color:#ffdd7c}
|
||||
.blame-info.heat4{ border-right-color:#fba447}
|
||||
.blame-info.heat5{ border-right-color:#f68736}
|
||||
.blame-info.heat6{ border-right-color:#f37636}
|
||||
.blame-info.heat7{ border-right-color:#ca6632}
|
||||
.blame-info.heat8{ border-right-color:#c0513f}
|
||||
.blame-info.heat9{ border-right-color:#a2503a}
|
||||
.blame-info.heat10{border-right-color:#793738}
|
||||
|
||||
.heat1{background-color:#ffeca7}
|
||||
.heat2{background-color:#ffdd8c}
|
||||
.heat3{background-color:#ffdd7c}
|
||||
.heat4{background-color:#fba447}
|
||||
.heat5{background-color:#f68736}
|
||||
.heat6{background-color:#f37636}
|
||||
.heat7{background-color:#ca6632}
|
||||
.heat8{background-color:#c0513f}
|
||||
.heat9{background-color:#a2503a}
|
||||
.heat10{background-color:#793738}
|
||||
|
||||
/****************************************************************************/
|
||||
/* Mobile */
|
||||
/****************************************************************************/
|
||||
@media (max-width: 767px) {
|
||||
body>form#search {
|
||||
margin: 0 -20px 20px -20px;
|
||||
}
|
||||
body>div.dashboard-nav {
|
||||
margin: 0 -20px 20px -20px;
|
||||
padding: 0 10px;
|
||||
}
|
||||
.container {
|
||||
width: auto !important;
|
||||
}
|
||||
.nav-pills-group .pull-right #search-filter-box {
|
||||
width: 90% !important;
|
||||
position: absolute;
|
||||
left: 20px;
|
||||
right: 20px;
|
||||
margin-top: 42px;
|
||||
}
|
||||
.nav-pills-group .pull-right form#search-filter-form {
|
||||
margin-bottom: 60px !important;
|
||||
}
|
||||
.table-issues a.button-link {
|
||||
width: 42px;
|
||||
height: 16px;
|
||||
overflow: hidden;
|
||||
display: inline-block;
|
||||
}
|
||||
.nav-tabs a.btn[href$="/_edit"] {
|
||||
width: 24px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
padding: 4px 6px;
|
||||
margin: 3px 4px 0 0;
|
||||
}
|
||||
body>div.container.body {
|
||||
margin: 0 -12px 40px -12px;
|
||||
}
|
||||
.container.body>div[style="width: 170px;"]{
|
||||
width: 32px !important;
|
||||
margin-right: -5px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.container.body>div[style="margin-right: 180px;"]{
|
||||
margin-right: 32px !important;
|
||||
}
|
||||
.container.body>div[style="width: 170px;"] .sidemenu i, .container.body>div[style="width: 170px;"] .sidemenu img {
|
||||
padding-right: 5px;
|
||||
}
|
||||
/* .container.body>div[style="width: 170px;"] .small,.container.body>div[style="width: 170px;"] .input-append, .container.body>div[style="width: 170px;"] div[style="margin-top: 10px;"] { */
|
||||
.container.body>div[style="width: 170px;"] .small,.container.body>div[style="width: 170px;"] .input-append {
|
||||
|
||||
display: none;
|
||||
}
|
||||
|
||||
.container.body>div[style="width: 170px;"] div[style="margin-top: 10px;"] a.btn{
|
||||
width: 26px !important;
|
||||
padding: 2px;
|
||||
}
|
||||
.container.body>div[style="width: 170px;"] div[style="margin-top: 10px;"] a.btn i {
|
||||
margin: 5px 10px 5px 6px;
|
||||
}
|
||||
body>.container>#fork-form{
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
|
||||
BIN
src/main/webapp/assets/common/images/checker.png
Normal file
BIN
src/main/webapp/assets/common/images/checker.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 949 B |
@@ -12,6 +12,12 @@ $(function(){
|
||||
$('a[data-toggle=tooltip]').tooltip();
|
||||
$('li[data-toggle=tooltip]').tooltip();
|
||||
|
||||
// activate hotkey
|
||||
$('a[data-hotkey]').each(function(){
|
||||
var target = this;
|
||||
$(document).bind('keydown', $(target).data('hotkey'), function(){ target.click(); });
|
||||
});
|
||||
|
||||
// anchor icon for markdown
|
||||
$('.markdown-head').mouseenter(function(e){
|
||||
$(e.target).children('a.markdown-anchor-link').show();
|
||||
@@ -334,3 +340,366 @@ $.extend(JsDiffRender.prototype,{
|
||||
return ret;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* scroll target into view ( on bottom edge, or on top edge)
|
||||
*/
|
||||
function scrollIntoView(target){
|
||||
target = $(target);
|
||||
var $window = $(window);
|
||||
var docViewTop = $window.scrollTop();
|
||||
var docViewBottom = docViewTop + $window.height();
|
||||
|
||||
var elemTop = target.offset().top;
|
||||
var elemBottom = elemTop + target.height();
|
||||
|
||||
if(elemBottom > docViewBottom){
|
||||
$('html, body').scrollTop(elemBottom - $window.height());
|
||||
}else if(elemTop < docViewTop){
|
||||
$('html, body').scrollTop(elemTop);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* escape html
|
||||
*/
|
||||
function escapeHtml(text){
|
||||
return text.replace(/&/g,'&').replace(/</g,'<').replace(/"/g,'"').replace(/>/g,'>');
|
||||
}
|
||||
|
||||
/**
|
||||
* calculate string ranking for path.
|
||||
* Original ported from:
|
||||
* http://joshaven.com/string_score
|
||||
* https://github.com/joshaven/string_score
|
||||
*
|
||||
* Copyright (C) 2009-2011 Joshaven Potter <yourtech@@gmail.com>
|
||||
* Special thanks to all of the contributors listed here https://github.com/joshaven/string_score
|
||||
* MIT license: http://www.opensource.org/licenses/mit-license.php
|
||||
*/
|
||||
function string_score(string, word) {
|
||||
'use strict';
|
||||
var zero = {score:0,matchingPositions:[]};
|
||||
|
||||
// If the string is equal to the word, perfect match.
|
||||
if (string === word || word === "") { return {score:1, matchingPositions:[]}; }
|
||||
|
||||
var lString = string.toUpperCase(),
|
||||
strLength = string.length,
|
||||
lWord = word.toUpperCase(),
|
||||
wordLength = word.length;
|
||||
|
||||
return calc(zero, 0, 0, 0, 0, []);
|
||||
function calc(score, startAt, skip, runningScore, i, matchingPositions){
|
||||
if( i < wordLength) {
|
||||
var charScore = 0;
|
||||
|
||||
// Find next first case-insensitive match of a character.
|
||||
var idxOf = lString.indexOf(lWord[i], skip);
|
||||
|
||||
if (-1 === idxOf) { return score; }
|
||||
score = calc(score, startAt, idxOf+1, runningScore, i, matchingPositions);
|
||||
if (startAt === idxOf) {
|
||||
// Consecutive letter & start-of-string Bonus
|
||||
charScore = 0.8;
|
||||
} else {
|
||||
charScore = 0.1;
|
||||
|
||||
// Acronym Bonus
|
||||
// Weighing Logic: Typing the first character of an acronym is as if you
|
||||
// preceded it with two perfect character matches.
|
||||
if (/^[^A-Za-z0-9]/.test(string[idxOf - 1])){
|
||||
charScore += 0.7;
|
||||
}else if(string[idxOf]==lWord[i]) {
|
||||
// Upper case bonus
|
||||
charScore += 0.2;
|
||||
// Camel case bonus
|
||||
if(/^[a-z]/.test(string[idxOf - 1])){
|
||||
charScore += 0.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Same case bonus.
|
||||
if (string[idxOf] === word[i]) { charScore += 0.1; }
|
||||
|
||||
// next round
|
||||
return calc(score, idxOf + 1, idxOf + 1, runningScore + charScore, i+1, matchingPositions.concat(idxOf));
|
||||
}else{
|
||||
// skip non match folder
|
||||
var effectiveLength = strLength;
|
||||
if(matchingPositions.length){
|
||||
var lastSlash = string.lastIndexOf('/',matchingPositions[0]);
|
||||
if(lastSlash!==-1){
|
||||
effectiveLength = strLength-lastSlash;
|
||||
}
|
||||
}
|
||||
// Reduce penalty for longer strings.
|
||||
var finalScore = 0.5 * (runningScore / effectiveLength + runningScore / wordLength);
|
||||
|
||||
if ((lWord[0] === lString[0]) && (finalScore < 0.85)) {
|
||||
finalScore += 0.15;
|
||||
}
|
||||
if(score.score >= finalScore){
|
||||
return score;
|
||||
}
|
||||
return {score:finalScore, matchingPositions:matchingPositions};
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* sort by string_score.
|
||||
* @param word {String} search word
|
||||
* @param strings {Array[String]} search targets
|
||||
* @param limit {Integer} result limit
|
||||
* @return {Array[{score:"float matching score", string:"string target string", matchingPositions:"Array[Interger] matchng positions"}]}
|
||||
*/
|
||||
function string_score_sort(word, strings, limit){
|
||||
var ret = [], i=0, l = (word==="")?Math.min(strings.length, limit):strings.length;
|
||||
for(; i < l; i++){
|
||||
var score = string_score(strings[i],word);
|
||||
if(score.score){
|
||||
score.string = strings[i];
|
||||
ret.push(score);
|
||||
}
|
||||
}
|
||||
ret.sort(function(a,b){
|
||||
var s = b.score - a.score;
|
||||
if(s === 0){
|
||||
return a.string > b.string ? 1 : -1;
|
||||
}
|
||||
return s;
|
||||
});
|
||||
ret = ret.slice(0,limit);
|
||||
return ret;
|
||||
}
|
||||
/**
|
||||
* highlight by result.
|
||||
* @param score {string:"string target string", matchingPositions:"Array[Interger] matchng positions"}
|
||||
* @param highlight tag ex: '<b>'
|
||||
* @return array of highlighted html elements.
|
||||
*/
|
||||
function string_score_highlight(result, tag){
|
||||
var str = result.string, msp=0;
|
||||
return hilight([], 0, result.matchingPositions[msp]);
|
||||
function hilight(html, c, mpos){
|
||||
if(mpos === undefined){
|
||||
return html.concat(document.createTextNode(str.substr(c)));
|
||||
}else{
|
||||
return hilight(html.concat([
|
||||
document.createTextNode(str.substring(c,mpos)),
|
||||
$(tag).text(str[mpos])]),
|
||||
mpos+1, result.matchingPositions[++msp]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************/
|
||||
/* Diff */
|
||||
/****************************************************************************/
|
||||
// add naturalWidth and naturalHeight for ie 8
|
||||
function setNatural(img) {
|
||||
if(typeof img.naturalWidth == 'undefined'){
|
||||
var tmp = new Image();
|
||||
tmp.src = img.src;
|
||||
img.naturalWidth = tmp.width;
|
||||
img.naturalHeight = tmp.height;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* onload handler
|
||||
* @param img <img>
|
||||
*/
|
||||
function onLoadedDiffImages(img){
|
||||
setNatural(img);
|
||||
img = $(img);
|
||||
img.show();
|
||||
var tb = img.parents(".diff-image-render");
|
||||
// Find images. If the image has not loaded yet, value is undefined.
|
||||
var old = tb.find(".diff-old img.diff-image:visible")[0];
|
||||
var neo = tb.find(".diff-new img.diff-image:visible")[0];
|
||||
imageDiff.appendImageMeta(tb, old, neo);
|
||||
if(old && neo){
|
||||
imageDiff.createToolSelector(old, neo).appendTo(tb.parent());
|
||||
}
|
||||
}
|
||||
var imageDiff ={
|
||||
/** append image meta div after image nodes.
|
||||
* @param tb <div class="diff-image-2up">
|
||||
* @param old <img>||undefined
|
||||
* @param neo <img>||undefined
|
||||
*/
|
||||
appendImageMeta:function(tb, old, neo){
|
||||
old = old || {};
|
||||
neo = neo || {};
|
||||
tb.find(".diff-meta").remove();
|
||||
// before loaded, image is not visible.
|
||||
tb.find("img.diff-image:visible").each(function(){
|
||||
var div = $('<p class="diff-meta"><b>W:</b><span class="w"></span> | <b>W:</b><span class="h"></span></p>');
|
||||
div.find('.w').text(this.naturalWidth+"px").toggleClass("diff", old.naturalWidth != neo.naturalWidth);
|
||||
div.find('.h').text(this.naturalHeight+"px").toggleClass("diff", old.naturalHeight != neo.naturalHeight);
|
||||
div.appendTo(this.parentNode);
|
||||
});
|
||||
},
|
||||
/** check this browser can use canvas tag.
|
||||
*/
|
||||
hasCanvasSupport:function(){
|
||||
if(!this.hasCanvasSupport.hasOwnProperty('resultCache')){
|
||||
this.hasCanvasSupport.resultCache = (typeof $('<canvas>')[0].getContext)=='function';
|
||||
}
|
||||
return this.hasCanvasSupport.resultCache;
|
||||
},
|
||||
/** create toolbar
|
||||
* @param old <img>
|
||||
* @param neo <img>
|
||||
* @return jQuery(<ul class="image-diff-tools">)
|
||||
*/
|
||||
createToolSelector:function(old, neo){
|
||||
var self = this;
|
||||
return $('<ul class="image-diff-tools">'+
|
||||
'<li data-mode="diff2up" class="active">2-up</li>'+
|
||||
'<li data-mode="swipe">Swipe</li>'+
|
||||
'<li data-mode="onion">Onion Skin</li>'+
|
||||
'<li data-mode="difference" class="need-canvas">Difference</li>'+
|
||||
'<li data-mode="blink">Blink</li>'+
|
||||
'</ul>')
|
||||
.toggleClass('no-canvas', !this.hasCanvasSupport())
|
||||
.on('click', 'li', function(e){
|
||||
var td = $(this).parents("td");
|
||||
$(e.delegateTarget).find('li').each(function(){ $(this).toggleClass('active',this == e.target); });
|
||||
var mode = $(e.target).data('mode');
|
||||
td.find(".diff-image-render").hide();
|
||||
// create div if not created yet
|
||||
if(td.find(".diff-image-render."+mode).show().length===0){
|
||||
self[mode](old, neo).insertBefore(e.delegateTarget).addClass("diff-image-render");
|
||||
}
|
||||
return false;
|
||||
});
|
||||
},
|
||||
/** (private) calc size from images and css (const)
|
||||
* @param old <img>
|
||||
* @param neo <img>
|
||||
*/
|
||||
calcSizes:function(old, neo){
|
||||
var maxWidth = 869 - 20 - 20 - 4; // set by css
|
||||
var h = Math.min(Math.max(old.naturalHeight, neo.naturalHeight),maxWidth);
|
||||
var w = Math.min(Math.max(old.naturalWidth, neo.naturalWidth),maxWidth);
|
||||
var oldRate = Math.min(h/old.naturalHeight, w/old.naturalWidth);
|
||||
var neoRate = Math.min(h/neo.naturalHeight, w/neo.naturalWidth);
|
||||
var neoW = neo.naturalWidth*neoRate;
|
||||
var neoH = neo.naturalHeight*neoRate;
|
||||
var oldW = old.naturalWidth*oldRate;
|
||||
var oldH = old.naturalHeight*oldRate;
|
||||
var paddingLeft = (maxWidth/2)-Math.max(neoW,oldW)/2;
|
||||
return {
|
||||
height:Math.max(oldH, neoH),
|
||||
width:w,
|
||||
padding:paddingLeft,
|
||||
paddingTop:20, // set by css
|
||||
barWidth:200, // set by css
|
||||
old:{ rate:oldRate, width:oldW, height:oldH },
|
||||
neo:{ rate:neoRate, width:neoW, height:neoH }
|
||||
};
|
||||
},
|
||||
/** (private) create div
|
||||
* @param old <img>
|
||||
* @param neo <img>
|
||||
*/
|
||||
stack:function(old, neo){
|
||||
var size = this.calcSizes(old, neo);
|
||||
var diffNew = $('<div class="diff-new">')
|
||||
.append($("<img>").attr('src',neo.src).css({width:size.neo.width, height:size.neo.height}));
|
||||
var diffOld = $('<div class="diff-old">')
|
||||
.append($("<img>").attr('src',old.src).css({width:size.old.width, height:size.old.height}));
|
||||
var handle = $('<span class="diff-swipe-handle icon icon-resize-horizontal"></span>')
|
||||
.css({marginTop:size.height-5});
|
||||
var bar = $('<hr class="diff-silde-bar">').css({top:size.height+size.paddingTop});
|
||||
var div = $('<div class="diff-image-stack">')
|
||||
.css({height:size.height+size.paddingTop, paddingLeft:size.padding})
|
||||
.append(diffOld, diffNew, bar, handle);
|
||||
return {
|
||||
neo:diffNew,
|
||||
old:diffOld,
|
||||
size:size,
|
||||
handle:handle,
|
||||
bar:bar,
|
||||
div:div,
|
||||
/* add event listener 'on mousemove' */
|
||||
onMoveHandleOnBar:function(callback){
|
||||
div.on('mousemove',function(e){
|
||||
var x = Math.max(Math.min((e.pageX - bar.offset().left), size.barWidth), 0);
|
||||
handle.css({left:x});
|
||||
callback(x, e);
|
||||
});
|
||||
}
|
||||
};
|
||||
},
|
||||
/** create swipe box
|
||||
* @param old <img>
|
||||
* @param neo <img>
|
||||
* @return jQuery(<div class="diff-image-stack swipe">)
|
||||
*/
|
||||
swipe:function(old, neo){
|
||||
var stack = this.stack(old, neo);
|
||||
function setX(x){
|
||||
stack.neo.css({width:x});
|
||||
stack.handle.css({left:x+stack.size.padding});
|
||||
}
|
||||
setX(stack.size.neo.width/2);
|
||||
stack.div.on('mousemove',function(e){
|
||||
setX(Math.max(Math.min(e.pageX - stack.neo.offset().left, stack.size.neo.width),0));
|
||||
});
|
||||
return stack.div.addClass('swipe');
|
||||
},
|
||||
/** create blink box
|
||||
* @param old <img>
|
||||
* @param neo <img>
|
||||
* @return jQuery(<div class="diff-image-stack blink">)
|
||||
*/
|
||||
blink:function(old, neo){
|
||||
var stack = this.stack(old, neo);
|
||||
stack.onMoveHandleOnBar(function(x){
|
||||
stack.neo.toggle(Math.floor(x) % 2 === 0);
|
||||
});
|
||||
return stack.div.addClass('blink');
|
||||
},
|
||||
/** create onion skin box
|
||||
* @param old <img>
|
||||
* @param neo <img>
|
||||
* @return jQuery(<div class="diff-image-stack onion">)
|
||||
*/
|
||||
onion:function(old, neo){
|
||||
var stack = this.stack(old, neo);
|
||||
stack.neo.css({opacity:0.5});
|
||||
stack.onMoveHandleOnBar(function(x){
|
||||
stack.neo.css({opacity:x/stack.size.barWidth});
|
||||
});
|
||||
return stack.div.addClass('onion');
|
||||
},
|
||||
/** create difference box
|
||||
* @param old <img>
|
||||
* @param neo <img>
|
||||
* @return jQuery(<div class="diff-image-stack difference">)
|
||||
*/
|
||||
difference:function(old, neo){
|
||||
var size = this.calcSizes(old,neo);
|
||||
var canvas = $('<canvas>').attr({width:size.width, height:size.height})[0];
|
||||
var context = canvas.getContext('2d');
|
||||
|
||||
context.clearRect(0, 0, size.width, size.height);
|
||||
context.drawImage(neo, 0, 0, size.neo.width, size.neo.height);
|
||||
var neoData = context.getImageData(0, 0, size.neo.width, size.neo.height).data;
|
||||
context.clearRect(0, 0, size.width, size.height);
|
||||
|
||||
context.drawImage(old, 0, 0, size.old.width, size.old.height);
|
||||
var c = context.getImageData(0, 0, size.neo.width, size.neo.height);
|
||||
var cData = c.data;
|
||||
for (var i = cData.length -1; i>0; i --){
|
||||
cData[i] = (i%4===3) ? Math.max(cData[i], neoData[i]) : Math.abs(cData[i] - neoData[i]);
|
||||
}
|
||||
context.putImageData(c, 0, 0);
|
||||
|
||||
return $('<div class="diff-image-stack difference">').append(canvas);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
204
src/main/webapp/assets/vendors/jquery-hotkeys/jquery.hotkeys.js
vendored
Normal file
204
src/main/webapp/assets/vendors/jquery-hotkeys/jquery.hotkeys.js
vendored
Normal file
@@ -0,0 +1,204 @@
|
||||
/*jslint browser: true*/
|
||||
/*jslint jquery: true*/
|
||||
|
||||
/*
|
||||
* jQuery Hotkeys Plugin
|
||||
* Copyright 2010, John Resig
|
||||
* Dual licensed under the MIT or GPL Version 2 licenses.
|
||||
*
|
||||
* Based upon the plugin by Tzury Bar Yochay:
|
||||
* https://github.com/tzuryby/jquery.hotkeys
|
||||
*
|
||||
* Original idea by:
|
||||
* Binny V A, http://www.openjs.com/scripts/events/keyboard_shortcuts/
|
||||
*/
|
||||
|
||||
/*
|
||||
* One small change is: now keys are passed by object { keys: '...' }
|
||||
* Might be useful, when you want to pass some other data to your handler
|
||||
*/
|
||||
|
||||
(function(jQuery) {
|
||||
|
||||
jQuery.hotkeys = {
|
||||
version: "0.8",
|
||||
|
||||
specialKeys: {
|
||||
8: "backspace",
|
||||
9: "tab",
|
||||
10: "return",
|
||||
13: "return",
|
||||
16: "shift",
|
||||
17: "ctrl",
|
||||
18: "alt",
|
||||
19: "pause",
|
||||
20: "capslock",
|
||||
27: "esc",
|
||||
32: "space",
|
||||
33: "pageup",
|
||||
34: "pagedown",
|
||||
35: "end",
|
||||
36: "home",
|
||||
37: "left",
|
||||
38: "up",
|
||||
39: "right",
|
||||
40: "down",
|
||||
45: "insert",
|
||||
46: "del",
|
||||
59: ";",
|
||||
61: "=",
|
||||
96: "0",
|
||||
97: "1",
|
||||
98: "2",
|
||||
99: "3",
|
||||
100: "4",
|
||||
101: "5",
|
||||
102: "6",
|
||||
103: "7",
|
||||
104: "8",
|
||||
105: "9",
|
||||
106: "*",
|
||||
107: "+",
|
||||
109: "-",
|
||||
110: ".",
|
||||
111: "/",
|
||||
112: "f1",
|
||||
113: "f2",
|
||||
114: "f3",
|
||||
115: "f4",
|
||||
116: "f5",
|
||||
117: "f6",
|
||||
118: "f7",
|
||||
119: "f8",
|
||||
120: "f9",
|
||||
121: "f10",
|
||||
122: "f11",
|
||||
123: "f12",
|
||||
144: "numlock",
|
||||
145: "scroll",
|
||||
173: "-",
|
||||
186: ";",
|
||||
187: "=",
|
||||
188: ",",
|
||||
189: "-",
|
||||
190: ".",
|
||||
191: "/",
|
||||
192: "`",
|
||||
219: "[",
|
||||
220: "\\",
|
||||
221: "]",
|
||||
222: "'"
|
||||
},
|
||||
|
||||
shiftNums: {
|
||||
"`": "~",
|
||||
"1": "!",
|
||||
"2": "@",
|
||||
"3": "#",
|
||||
"4": "$",
|
||||
"5": "%",
|
||||
"6": "^",
|
||||
"7": "&",
|
||||
"8": "*",
|
||||
"9": "(",
|
||||
"0": ")",
|
||||
"-": "_",
|
||||
"=": "+",
|
||||
";": ": ",
|
||||
"'": "\"",
|
||||
",": "<",
|
||||
".": ">",
|
||||
"/": "?",
|
||||
"\\": "|"
|
||||
},
|
||||
|
||||
// excludes: button, checkbox, file, hidden, image, password, radio, reset, search, submit, url
|
||||
textAcceptingInputTypes: [
|
||||
"text", "password", "number", "email", "url", "range", "date", "month", "week", "time", "datetime",
|
||||
"datetime-local", "search", "color", "tel"],
|
||||
|
||||
// default input types not to bind to unless bound directly
|
||||
textInputTypes: /textarea|input|select/i,
|
||||
|
||||
options: {
|
||||
filterInputAcceptingElements: true,
|
||||
filterTextInputs: true,
|
||||
filterContentEditable: true
|
||||
}
|
||||
};
|
||||
|
||||
function keyHandler(handleObj) {
|
||||
if (typeof handleObj.data === "string") {
|
||||
handleObj.data = {
|
||||
keys: handleObj.data
|
||||
};
|
||||
}
|
||||
|
||||
// Only care when a possible input has been specified
|
||||
if (!handleObj.data || !handleObj.data.keys || typeof handleObj.data.keys !== "string") {
|
||||
return;
|
||||
}
|
||||
|
||||
var origHandler = handleObj.handler,
|
||||
keys = handleObj.data.keys.toLowerCase().split(" ");
|
||||
|
||||
handleObj.handler = function(event) {
|
||||
// Don't fire in text-accepting inputs that we didn't directly bind to
|
||||
if (this !== event.target &&
|
||||
(jQuery.hotkeys.options.filterInputAcceptingElements &&
|
||||
jQuery.hotkeys.textInputTypes.test(event.target.nodeName) ||
|
||||
(jQuery.hotkeys.options.filterContentEditable && jQuery(event.target).attr('contenteditable')) ||
|
||||
(jQuery.hotkeys.options.filterTextInputs &&
|
||||
jQuery.inArray(event.target.type, jQuery.hotkeys.textAcceptingInputTypes) > -1))) {
|
||||
return;
|
||||
}
|
||||
|
||||
var special = event.type !== "keypress" && jQuery.hotkeys.specialKeys[event.which],
|
||||
character = String.fromCharCode(event.which).toLowerCase(),
|
||||
modif = "",
|
||||
possible = {};
|
||||
|
||||
jQuery.each(["alt", "ctrl", "shift"], function(index, specialKey) {
|
||||
|
||||
if (event[specialKey + 'Key'] && special !== specialKey) {
|
||||
modif += specialKey + '+';
|
||||
}
|
||||
});
|
||||
|
||||
// metaKey is triggered off ctrlKey erronously
|
||||
if (event.metaKey && !event.ctrlKey && special !== "meta") {
|
||||
modif += "meta+";
|
||||
}
|
||||
|
||||
if (event.metaKey && special !== "meta" && modif.indexOf("alt+ctrl+shift+") > -1) {
|
||||
modif = modif.replace("alt+ctrl+shift+", "hyper+");
|
||||
}
|
||||
|
||||
if (special) {
|
||||
possible[modif + special] = true;
|
||||
}
|
||||
else {
|
||||
possible[modif + character] = true;
|
||||
possible[modif + jQuery.hotkeys.shiftNums[character]] = true;
|
||||
|
||||
// "$" can be triggered as "Shift+4" or "Shift+$" or just "$"
|
||||
if (modif === "shift+") {
|
||||
possible[jQuery.hotkeys.shiftNums[character]] = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0, l = keys.length; i < l; i++) {
|
||||
if (possible[keys[i]]) {
|
||||
return origHandler.apply(this, arguments);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
jQuery.each(["keydown", "keyup", "keypress"], function() {
|
||||
jQuery.event.special[this] = {
|
||||
add: keyHandler
|
||||
};
|
||||
});
|
||||
|
||||
})(jQuery || this.jQuery || window.jQuery);
|
||||
File diff suppressed because it is too large
Load Diff
@@ -66,7 +66,7 @@ table.diff thead th.texttitle {
|
||||
}
|
||||
table.diff tbody td {
|
||||
padding:0px .4em;
|
||||
padding-top:.4em;
|
||||
padding-top:.2em;
|
||||
vertical-align:top;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
@@ -90,11 +90,12 @@ class JsonFormatSpec extends Specification {
|
||||
user = apiUser,
|
||||
body= "Me too",
|
||||
created_at= date1,
|
||||
updated_at= date1)
|
||||
updated_at= date1)(RepositoryName("octocat","Hello-World"), 100)
|
||||
val apiCommentJson = s"""{
|
||||
"id": 1,
|
||||
"body": "Me too",
|
||||
"user": $apiUserJson,
|
||||
"html_url" : "${context.baseUrl}/octocat/Hello-World/issues/100#comment-1",
|
||||
"created_at": "2011-04-14T16:00:49Z",
|
||||
"updated_at": "2011-04-14T16:00:49Z"
|
||||
}"""
|
||||
@@ -157,13 +158,15 @@ class JsonFormatSpec extends Specification {
|
||||
state = "open",
|
||||
body = "I'm having a problem with this.",
|
||||
created_at = date1,
|
||||
updated_at = date1)
|
||||
updated_at = date1)(RepositoryName("octocat","Hello-World"))
|
||||
val apiIssueJson = s"""{
|
||||
"number": 1347,
|
||||
"state": "open",
|
||||
"title": "Found a bug",
|
||||
"body": "I'm having a problem with this.",
|
||||
"user": $apiUserJson,
|
||||
"comments_url": "${context.baseUrl}/api/v3/repos/octocat/Hello-World/issues/1347/comments",
|
||||
"html_url": "${context.baseUrl}/octocat/Hello-World/issues/1347",
|
||||
"created_at": "2011-04-14T16:00:49Z",
|
||||
"updated_at": "2011-04-14T16:00:49Z"
|
||||
}"""
|
||||
|
||||
Reference in New Issue
Block a user