mirror of
https://github.com/gitbucket/gitbucket.git
synced 2025-11-07 14:05:52 +01:00
(refs #13)File editing (add, edit and delete) in repository viewer is available.
This commit is contained in:
@@ -15,7 +15,7 @@ import org.eclipse.jgit.treewalk._
|
|||||||
import java.util.zip.{ZipEntry, ZipOutputStream}
|
import java.util.zip.{ZipEntry, ZipOutputStream}
|
||||||
import jp.sf.amateras.scalatra.forms._
|
import jp.sf.amateras.scalatra.forms._
|
||||||
import org.eclipse.jgit.dircache.DirCache
|
import org.eclipse.jgit.dircache.DirCache
|
||||||
import org.eclipse.jgit.revwalk.RevWalk
|
import org.eclipse.jgit.revwalk.{RevCommit, RevWalk}
|
||||||
|
|
||||||
class RepositoryViewerController extends RepositoryViewerControllerBase
|
class RepositoryViewerController extends RepositoryViewerControllerBase
|
||||||
with RepositoryService with AccountService with ActivityService with ReferrerAuthenticator with CollaboratorsAuthenticator
|
with RepositoryService with AccountService with ActivityService with ReferrerAuthenticator with CollaboratorsAuthenticator
|
||||||
@@ -26,14 +26,40 @@ class RepositoryViewerController extends RepositoryViewerControllerBase
|
|||||||
trait RepositoryViewerControllerBase extends ControllerBase {
|
trait RepositoryViewerControllerBase extends ControllerBase {
|
||||||
self: RepositoryService with AccountService with ActivityService with ReferrerAuthenticator with CollaboratorsAuthenticator =>
|
self: RepositoryService with AccountService with ActivityService with ReferrerAuthenticator with CollaboratorsAuthenticator =>
|
||||||
|
|
||||||
case class EditorForm(content: String, message: Option[String], charset: String)
|
case class EditorForm(
|
||||||
|
branch: String,
|
||||||
|
path: String,
|
||||||
|
content: String,
|
||||||
|
message: Option[String],
|
||||||
|
charset: String,
|
||||||
|
newFileName: String,
|
||||||
|
oldFileName: Option[String]
|
||||||
|
)
|
||||||
|
|
||||||
|
case class DeleteForm(
|
||||||
|
branch: String,
|
||||||
|
path: String,
|
||||||
|
message: Option[String],
|
||||||
|
fileName: String
|
||||||
|
)
|
||||||
|
|
||||||
val editorForm = mapping(
|
val editorForm = mapping(
|
||||||
"content" -> trim(label("Content", text())),
|
"branch" -> trim(label("Branch", text(required))),
|
||||||
"message" -> trim(label("Messgae", optional(text()))),
|
"path" -> trim(label("Path", text())),
|
||||||
"charset" -> trim(label("Charset", text()))
|
"content" -> trim(label("Content", text(required))),
|
||||||
|
"message" -> trim(label("Message", optional(text()))),
|
||||||
|
"charset" -> trim(label("Charset", text(required))),
|
||||||
|
"newFileName" -> trim(label("Filename", text(required))),
|
||||||
|
"oldFileName" -> trim(label("Old filename", optional(text())))
|
||||||
)(EditorForm.apply)
|
)(EditorForm.apply)
|
||||||
|
|
||||||
|
val deleteForm = mapping(
|
||||||
|
"branch" -> trim(label("Branch", text(required))),
|
||||||
|
"path" -> trim(label("Path", text())),
|
||||||
|
"message" -> trim(label("Message", optional(text()))),
|
||||||
|
"fileName" -> trim(label("Filename", text(required)))
|
||||||
|
)(DeleteForm.apply)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns converted HTML from Markdown for preview.
|
* Returns converted HTML from Markdown for preview.
|
||||||
*/
|
*/
|
||||||
@@ -82,96 +108,68 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
get("/:owner/:repository/new/*")(collaboratorsOnly { repository =>
|
||||||
* Displays the file content of the specified branch or commit.
|
val (branch, path) = splitPath(repository, multiParams("splat").head)
|
||||||
*/
|
repo.html.editor(branch, repository, if(path.length == 0) Nil else path.split("/").toList,
|
||||||
|
None, JGitUtil.ContentInfo("text", None, Some("UTF-8")))
|
||||||
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/edit/*")(collaboratorsOnly { repository =>
|
get("/:owner/:repository/edit/*")(collaboratorsOnly { repository =>
|
||||||
val (id, path) = splitPath(repository, multiParams("splat").head)
|
val (branch, path) = splitPath(repository, multiParams("splat").head)
|
||||||
|
|
||||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
||||||
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id))
|
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(branch))
|
||||||
|
|
||||||
@scala.annotation.tailrec
|
getPathObjectId(git, path, revCommit).map { objectId =>
|
||||||
def getPathObjectId(path: String, walk: TreeWalk): Option[ObjectId] = walk.next match {
|
val paths = path.split("/")
|
||||||
case true if(walk.getPathString == path) => Some(walk.getObjectId(0))
|
repo.html.editor(branch, repository, paths.take(paths.size - 1).toList, Some(paths.last),
|
||||||
case true => getPathObjectId(path, walk)
|
JGitUtil.getContentInfo(git, path, objectId))
|
||||||
case false => None
|
|
||||||
}
|
|
||||||
|
|
||||||
using(new TreeWalk(git.getRepository)){ treeWalk =>
|
|
||||||
treeWalk.addTree(revCommit.getTree)
|
|
||||||
treeWalk.setRecursive(true)
|
|
||||||
getPathObjectId(path, treeWalk)
|
|
||||||
} map { objectId =>
|
|
||||||
// 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
|
|
||||||
|
|
||||||
val content = if(viewer == "other"){
|
|
||||||
if(bytes.isDefined && FileUtil.isText(bytes.get)){
|
|
||||||
// text
|
|
||||||
JGitUtil.ContentInfo("text", Some(StringUtil.convertFromByteArray(bytes.get)), Some(StringUtil.detectEncoding(bytes.get)))
|
|
||||||
} else {
|
|
||||||
// binary
|
|
||||||
JGitUtil.ContentInfo("binary", None, None)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// image or large
|
|
||||||
JGitUtil.ContentInfo(viewer, None, None)
|
|
||||||
}
|
|
||||||
|
|
||||||
repo.html.editor(id, repository, path.split("/").toList, content, new JGitUtil.CommitInfo(revCommit))
|
|
||||||
} getOrElse NotFound
|
} getOrElse NotFound
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
post("/:owner/:repository/edit/*", editorForm)(collaboratorsOnly { (form, repository) =>
|
get("/:owner/:repository/remove/*")(collaboratorsOnly { repository =>
|
||||||
val (id, path) = splitPath(repository, multiParams("splat").head)
|
val (branch, path) = splitPath(repository, multiParams("splat").head)
|
||||||
|
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
||||||
|
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(branch))
|
||||||
|
|
||||||
LockUtil.lock(s"${repository.owner}/${repository.name}"){
|
getPathObjectId(git, path, revCommit).map { objectId =>
|
||||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
val paths = path.split("/")
|
||||||
val loginAccount = context.loginAccount.get
|
repo.html.delete(branch, repository, paths.take(paths.size - 1).toList, paths.last,
|
||||||
val builder = DirCache.newInCore.builder()
|
JGitUtil.getContentInfo(git, path, objectId))
|
||||||
val inserter = git.getRepository.newObjectInserter()
|
} getOrElse NotFound
|
||||||
val headName = s"refs/heads/${id}"
|
|
||||||
val headTip = git.getRepository.resolve(s"refs/heads/${id}")
|
|
||||||
|
|
||||||
JGitUtil.processTree(git, headTip){ (treePath, tree) =>
|
|
||||||
if(treePath != path){
|
|
||||||
builder.add(JGitUtil.createDirCacheEntry(treePath, tree.getEntryFileMode, tree.getEntryObjectId))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.add(JGitUtil.createDirCacheEntry(path, FileMode.REGULAR_FILE,
|
|
||||||
inserter.insert(Constants.OBJ_BLOB, form.content.getBytes(form.charset))))
|
|
||||||
builder.finish()
|
|
||||||
|
|
||||||
val commitId = JGitUtil.createNewCommit(git, inserter, headTip, builder.getDirCache.writeTree(inserter),
|
|
||||||
loginAccount.fullName, loginAccount.mailAddress, form.message.getOrElse(s"Update ${path.split("/").last}"))
|
|
||||||
|
|
||||||
inserter.flush()
|
|
||||||
inserter.release()
|
|
||||||
|
|
||||||
// update refs
|
|
||||||
val refUpdate = git.getRepository.updateRef(headName)
|
|
||||||
refUpdate.setNewObjectId(commitId)
|
|
||||||
refUpdate.setForceUpdate(false)
|
|
||||||
refUpdate.setRefLogIdent(new PersonIdent(loginAccount.fullName, loginAccount.mailAddress))
|
|
||||||
//refUpdate.setRefLogMessage("merged", true)
|
|
||||||
refUpdate.update()
|
|
||||||
|
|
||||||
// record activity
|
|
||||||
recordPushActivity(repository.owner, repository.name, loginAccount.userName, id,
|
|
||||||
List(new CommitInfo(JGitUtil.getRevCommitFromId(git, commitId))))
|
|
||||||
|
|
||||||
// TODO invoke hook
|
|
||||||
|
|
||||||
redirect(s"/${repository.owner}/${repository.name}/blob/${id}/${path}")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
post("/:owner/:repository/create", editorForm)(collaboratorsOnly { (form, repository) =>
|
||||||
|
commitFile(repository, form.branch, form.path, Some(form.newFileName), None, form.content, form.charset,
|
||||||
|
form.message.getOrElse(s"Create ${form.newFileName}"))
|
||||||
|
|
||||||
|
redirect(s"/${repository.owner}/${repository.name}/blob/${form.branch}/${
|
||||||
|
if(form.path.length == 0) form.newFileName else s"${form.path}/${form.newFileName}"
|
||||||
|
}")
|
||||||
|
})
|
||||||
|
|
||||||
|
post("/:owner/:repository/update", editorForm)(collaboratorsOnly { (form, repository) =>
|
||||||
|
commitFile(repository, form.branch, form.path, Some(form.newFileName), form.oldFileName, form.content, form.charset,
|
||||||
|
if(form.oldFileName.exists(_ == form.newFileName)){
|
||||||
|
form.message.getOrElse(s"Update ${form.newFileName}")
|
||||||
|
} else {
|
||||||
|
form.message.getOrElse(s"Rename ${form.oldFileName.get} to ${form.newFileName}")
|
||||||
|
})
|
||||||
|
|
||||||
|
redirect(s"/${repository.owner}/${repository.name}/blob/${form.branch}/${
|
||||||
|
if(form.path.length == 0) form.newFileName else s"${form.path}/${form.newFileName}"
|
||||||
|
}")
|
||||||
|
})
|
||||||
|
|
||||||
|
post("/:owner/:repository/remove", deleteForm)(collaboratorsOnly { (form, repository) =>
|
||||||
|
commitFile(repository, form.branch, form.path, None, Some(form.fileName), "", "",
|
||||||
|
form.message.getOrElse(s"Delete ${form.fileName}"))
|
||||||
|
|
||||||
|
redirect(s"/${repository.owner}/${repository.name}/tree/${form.branch}${if(form.path.length == 0) "" else form.path}")
|
||||||
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays the file content of the specified branch or commit.
|
* Displays the file content of the specified branch or commit.
|
||||||
*/
|
*/
|
||||||
@@ -181,19 +179,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
||||||
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id))
|
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id))
|
||||||
|
getPathObjectId(git, path, revCommit).map { objectId =>
|
||||||
@scala.annotation.tailrec
|
|
||||||
def getPathObjectId(path: String, walk: TreeWalk): Option[ObjectId] = walk.next match {
|
|
||||||
case true if(walk.getPathString == path) => Some(walk.getObjectId(0))
|
|
||||||
case true => getPathObjectId(path, walk)
|
|
||||||
case false => None
|
|
||||||
}
|
|
||||||
|
|
||||||
using(new TreeWalk(git.getRepository)){ treeWalk =>
|
|
||||||
treeWalk.addTree(revCommit.getTree)
|
|
||||||
treeWalk.setRecursive(true)
|
|
||||||
getPathObjectId(path, treeWalk)
|
|
||||||
} map { objectId =>
|
|
||||||
if(raw){
|
if(raw){
|
||||||
// Download
|
// Download
|
||||||
defining(JGitUtil.getContentFromId(git, objectId, false).get){ bytes =>
|
defining(JGitUtil.getContentFromId(git, objectId, false).get){ bytes =>
|
||||||
@@ -201,26 +187,8 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
bytes
|
bytes
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Viewer
|
repo.html.blob(id, repository, path.split("/").toList, JGitUtil.getContentInfo(git, path, objectId),
|
||||||
val large = FileUtil.isLarge(git.getRepository.getObjectDatabase.open(objectId).getSize)
|
new JGitUtil.CommitInfo(revCommit), hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
||||||
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
|
|
||||||
|
|
||||||
val content = if(viewer == "other"){
|
|
||||||
if(bytes.isDefined && FileUtil.isText(bytes.get)){
|
|
||||||
// text
|
|
||||||
JGitUtil.ContentInfo("text", Some(StringUtil.convertFromByteArray(bytes.get)), Some(StringUtil.detectEncoding(bytes.get)))
|
|
||||||
} else {
|
|
||||||
// binary
|
|
||||||
JGitUtil.ContentInfo("binary", None, None)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// image or large
|
|
||||||
JGitUtil.ContentInfo(viewer, None, None)
|
|
||||||
}
|
|
||||||
|
|
||||||
repo.html.blob(id, repository, path.split("/").toList, content, new JGitUtil.CommitInfo(revCommit),
|
|
||||||
hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
|
||||||
}
|
}
|
||||||
} getOrElse NotFound
|
} getOrElse NotFound
|
||||||
}
|
}
|
||||||
@@ -395,4 +363,70 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private def commitFile(repository: service.RepositoryService.RepositoryInfo,
|
||||||
|
branch: String, path: String, newFileName: Option[String], oldFileName: Option[String],
|
||||||
|
content: String, charset: String, message: String) = {
|
||||||
|
|
||||||
|
val newPath = newFileName.map { newFileName => if(path.length == 0) newFileName else s"${path}/${newFileName}" }
|
||||||
|
val oldPath = oldFileName.map { oldFileName => if(path.length == 0) oldFileName else s"${path}/${oldFileName}" }
|
||||||
|
|
||||||
|
LockUtil.lock(s"${repository.owner}/${repository.name}"){
|
||||||
|
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
||||||
|
val loginAccount = context.loginAccount.get
|
||||||
|
val builder = DirCache.newInCore.builder()
|
||||||
|
val inserter = git.getRepository.newObjectInserter()
|
||||||
|
val headName = s"refs/heads/${branch}"
|
||||||
|
val headTip = git.getRepository.resolve(s"refs/heads/${branch}")
|
||||||
|
|
||||||
|
JGitUtil.processTree(git, headTip){ (path, tree) =>
|
||||||
|
if(path != newPath && !oldPath.exists(_ == path)){
|
||||||
|
builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newPath.foreach { newPath =>
|
||||||
|
builder.add(JGitUtil.createDirCacheEntry(newPath, FileMode.REGULAR_FILE,
|
||||||
|
inserter.insert(Constants.OBJ_BLOB, content.getBytes(charset))))
|
||||||
|
builder.finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
val commitId = JGitUtil.createNewCommit(git, inserter, headTip, builder.getDirCache.writeTree(inserter),
|
||||||
|
loginAccount.fullName, loginAccount.mailAddress, message)
|
||||||
|
|
||||||
|
inserter.flush()
|
||||||
|
inserter.release()
|
||||||
|
|
||||||
|
// update refs
|
||||||
|
val refUpdate = git.getRepository.updateRef(headName)
|
||||||
|
refUpdate.setNewObjectId(commitId)
|
||||||
|
refUpdate.setForceUpdate(false)
|
||||||
|
refUpdate.setRefLogIdent(new PersonIdent(loginAccount.fullName, loginAccount.mailAddress))
|
||||||
|
//refUpdate.setRefLogMessage("merged", true)
|
||||||
|
refUpdate.update()
|
||||||
|
|
||||||
|
// record activity
|
||||||
|
recordPushActivity(repository.owner, repository.name, loginAccount.userName, branch,
|
||||||
|
List(new CommitInfo(JGitUtil.getRevCommitFromId(git, commitId))))
|
||||||
|
|
||||||
|
// TODO invoke hook
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private def getPathObjectId(git: Git, path: String, revCommit: RevCommit): Option[ObjectId] = {
|
||||||
|
@scala.annotation.tailrec
|
||||||
|
def _getPathObjectId(path: String, walk: TreeWalk): Option[ObjectId] = walk.next match {
|
||||||
|
case true if(walk.getPathString == path) => Some(walk.getObjectId(0))
|
||||||
|
case true => _getPathObjectId(path, walk)
|
||||||
|
case false => None
|
||||||
|
}
|
||||||
|
|
||||||
|
using(new TreeWalk(git.getRepository)){ treeWalk =>
|
||||||
|
treeWalk.addTree(revCommit.getTree)
|
||||||
|
treeWalk.setRecursive(true)
|
||||||
|
_getPathObjectId(path, treeWalk)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -550,6 +550,26 @@ 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
|
||||||
|
|
||||||
|
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 {
|
||||||
|
// image or large
|
||||||
|
ContentInfo(viewer, None, None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get object content of the given object id as byte array from the Git repository.
|
* Get object content of the given object id as byte array from the Git repository.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -35,7 +35,10 @@
|
|||||||
}
|
}
|
||||||
<a class="btn btn-mini" href="?raw=true">Raw</a>
|
<a class="btn btn-mini" href="?raw=true">Raw</a>
|
||||||
<a class="btn btn-mini" href="@url(repository)/commits/@encodeRefName(branch)/@pathList.mkString("/")">History</a>
|
<a class="btn btn-mini" href="@url(repository)/commits/@encodeRefName(branch)/@pathList.mkString("/")">History</a>
|
||||||
</div>
|
@if(hasWritePermission){
|
||||||
|
<a class="btn btn-mini btn-danger" href="@url(repository)/remove/@encodeRefName(branch)/@pathList.mkString("/")">Delete</a>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
110
src/main/twirl/repo/delete.scala.html
Normal file
110
src/main/twirl/repo/delete.scala.html
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
@(branch: String,
|
||||||
|
repository: service.RepositoryService.RepositoryInfo,
|
||||||
|
pathList: List[String],
|
||||||
|
fileName: String,
|
||||||
|
content: util.JGitUtil.ContentInfo)(implicit context: app.Context)
|
||||||
|
@import context._
|
||||||
|
@import view.helpers._
|
||||||
|
@html.main(s"Deleting ${path} at ${fileName} - ${repository.owner}/${repository.name}", Some(repository)) {
|
||||||
|
@html.header("code", repository)
|
||||||
|
@tab(branch, repository, "files")
|
||||||
|
<form method="POST" action="@url(repository)/remove" validate="true">
|
||||||
|
<div class="head">
|
||||||
|
<a href="@url(repository)/tree/@encodeRefName(branch)">@repository.name</a> /
|
||||||
|
@pathList.zipWithIndex.map { case (section, i) =>
|
||||||
|
<a href="@url(repository)/tree/@encodeRefName(branch)/@pathList.take(i + 1).mkString("/")">@section</a> /
|
||||||
|
}
|
||||||
|
@fileName
|
||||||
|
<input type="hidden" name="fileName" id="fileName" value="@fileName"/>
|
||||||
|
<input type="hidden" name="branch" id="branch" value="@branch"/>
|
||||||
|
<input type="hidden" name="path" id="path" value="@pathList.mkString("/")"/>
|
||||||
|
</div>
|
||||||
|
<table class="table table-bordered">
|
||||||
|
<th style="font-weight: normal;" class="box-header">
|
||||||
|
@fileName
|
||||||
|
<div class="pull-right align-right">
|
||||||
|
<a href="@url(repository)/blob/@branch/@{(pathList ::: List(fileName)).mkString("/")}" class="btn btn-small">View</a>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<div id="diffText"></div>
|
||||||
|
<textarea id="newText" style="display: none;"></textarea>
|
||||||
|
<textarea id="oldText" style="display: none;">@content.content</textarea>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<div class="issue-avatar-image">@avatar(loginAccount.get.userName, 48)</div>
|
||||||
|
<div class="box issue-comment-box">
|
||||||
|
<div class="box-content">
|
||||||
|
<div>
|
||||||
|
<strong>Commit changes</strong>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input type="text" name="message" style="width: 98%;"/>
|
||||||
|
</div>
|
||||||
|
<div style="text-align: right;">
|
||||||
|
<a href="@url(repository)/blob/@encodeRefName(branch)/@pathList.mkString("/")" class="btn btn-danger">Cancel</a>
|
||||||
|
<input type="submit" id="commit" class="btn btn-success" value="Commit changes"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
}
|
||||||
|
<script type="text/javascript" src="@assets/jsdifflib/difflib.js"></script>
|
||||||
|
<script type="text/javascript" src="@assets/jsdifflib/diffview.js"></script>
|
||||||
|
<link href="@assets/jsdifflib/diffview.css" type="text/css" rel="stylesheet" />
|
||||||
|
<style type="text/css">
|
||||||
|
table.inlinediff {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.inlinediff thead {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
td.insert, td.equal, td.delete {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script>
|
||||||
|
function diffUsingJS(oldTextId, newTextId, outputId) {
|
||||||
|
// get the baseText and newText values from the two textboxes, and split them into lines
|
||||||
|
var oldText = document.getElementById(oldTextId).value;
|
||||||
|
if(oldText == ''){
|
||||||
|
var oldLines = [];
|
||||||
|
} else {
|
||||||
|
var oldLines = difflib.stringAsLines(oldText);
|
||||||
|
}
|
||||||
|
|
||||||
|
var newText = document.getElementById(newTextId).value
|
||||||
|
if(newText == ''){
|
||||||
|
var newLines = [];
|
||||||
|
} else {
|
||||||
|
var newLines = difflib.stringAsLines(newText);
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a SequenceMatcher instance that diffs the two sets of lines
|
||||||
|
var sm = new difflib.SequenceMatcher(oldLines, newLines);
|
||||||
|
|
||||||
|
// get the opcodes from the SequenceMatcher instance
|
||||||
|
// opcodes is a list of 3-tuples describing what changes should be made to the base text
|
||||||
|
// in order to yield the new text
|
||||||
|
var opcodes = sm.get_opcodes();
|
||||||
|
var diffoutputdiv = document.getElementById(outputId);
|
||||||
|
while (diffoutputdiv.firstChild) diffoutputdiv.removeChild(diffoutputdiv.firstChild);
|
||||||
|
|
||||||
|
// build the diff view and add it to the current DOM
|
||||||
|
diffoutputdiv.appendChild(diffview.buildView({
|
||||||
|
baseTextLines: oldLines,
|
||||||
|
newTextLines: newLines,
|
||||||
|
opcodes: opcodes,
|
||||||
|
contextSize: 4,
|
||||||
|
viewType: 1
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
$(function(){
|
||||||
|
diffUsingJS('oldText', 'newText', 'diffText');
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -1,47 +1,32 @@
|
|||||||
@(branch: String,
|
@(branch: String,
|
||||||
repository: service.RepositoryService.RepositoryInfo,
|
repository: service.RepositoryService.RepositoryInfo,
|
||||||
pathList: List[String],
|
pathList: List[String],
|
||||||
content: util.JGitUtil.ContentInfo,
|
fileName: Option[String],
|
||||||
latestCommit: util.JGitUtil.CommitInfo)(implicit context: app.Context)
|
content: util.JGitUtil.ContentInfo)(implicit context: app.Context)
|
||||||
@import context._
|
@import context._
|
||||||
@import view.helpers._
|
@import view.helpers._
|
||||||
@html.main(s"${repository.owner}/${repository.name}", Some(repository)) {
|
@html.main(if(fileName.isEmpty) "New File" else s"Editing ${path} at ${fileName} - ${repository.owner}/${repository.name}", Some(repository)) {
|
||||||
@html.header("code", repository)
|
@html.header("code", repository)
|
||||||
@tab(branch, repository, "files")
|
@tab(branch, repository, "files")
|
||||||
<div class="head">
|
<form method="POST" action="@url(repository)/@if(fileName.isEmpty){create}else{update}" validate="true">
|
||||||
<a href="@url(repository)/tree/@encodeRefName(branch)">@repository.name</a> /
|
<span class="error" id="error-newFileName"></span>
|
||||||
@pathList.zipWithIndex.map { case (section, i) =>
|
<div class="head">
|
||||||
@if(i == pathList.length - 1){
|
<a href="@url(repository)/tree/@encodeRefName(branch)">@repository.name</a> /
|
||||||
@section
|
@pathList.zipWithIndex.map { case (section, i) =>
|
||||||
} else {
|
|
||||||
<a href="@url(repository)/tree/@encodeRefName(branch)/@pathList.take(i + 1).mkString("/")">@section</a> /
|
<a href="@url(repository)/tree/@encodeRefName(branch)/@pathList.take(i + 1).mkString("/")">@section</a> /
|
||||||
}
|
}
|
||||||
|
<input type="text" name="newFileName" id="newFileName" placeholder="Name your file..." value="@fileName"/>
|
||||||
|
<input type="hidden" name="oldFileName" id="oldFileName" value="@fileName"/>
|
||||||
|
<input type="hidden" name="branch" id="branch" value="@branch"/>
|
||||||
|
<input type="hidden" name="path" id="path" value="@pathList.mkString("/")"/>
|
||||||
|
</div>
|
||||||
|
<style type="text/css" media="screen">
|
||||||
|
#editor {
|
||||||
|
width: 100%;
|
||||||
|
height: 600px;
|
||||||
}
|
}
|
||||||
</div>
|
</style>
|
||||||
<style type="text/css" media="screen">
|
|
||||||
#editor {
|
|
||||||
width: 100%;
|
|
||||||
height: 600px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<form method="POST" action="@url(repository)/edit/@encodeRefName(branch)/@pathList.mkString("/")">
|
|
||||||
<table class="table table-bordered">
|
<table class="table table-bordered">
|
||||||
@*
|
|
||||||
<tr>
|
|
||||||
<th style="font-weight: normal;">
|
|
||||||
<div class="pull-left">
|
|
||||||
@avatar(latestCommit, 20)
|
|
||||||
@user(latestCommit.committer, latestCommit.mailAddress, "username strong")
|
|
||||||
<span class="muted">@datetime(latestCommit.time)</span>
|
|
||||||
<a href="@url(repository)/commit/@latestCommit.id" class="commit-message">@link(latestCommit.summary, repository)</a>
|
|
||||||
</div>
|
|
||||||
<div class="btn-group pull-right">
|
|
||||||
<a class="btn btn-mini" href="?raw=true">Raw</a>
|
|
||||||
<a class="btn btn-mini" href="@url(repository)/commits/@encodeRefName(branch)/@pathList.mkString("/")">History</a>
|
|
||||||
</div>
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
*@
|
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<div id="editor"></div>
|
<div id="editor"></div>
|
||||||
@@ -55,14 +40,14 @@
|
|||||||
<strong>Commit changes</strong>
|
<strong>Commit changes</strong>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<input type="text" name="message" style="width: 98%;" placeholder="Update @pathList.last"/>
|
<input type="text" name="message" style="width: 98%;"/>
|
||||||
</div>
|
</div>
|
||||||
<div style="text-align: right;">
|
<div style="text-align: right;">
|
||||||
<a href="@url(repository)/blob/@encodeRefName(branch)/@pathList.mkString("/")" class="btn btn-danger">Cancel</a>
|
<a href="@url(repository)/blob/@encodeRefName(branch)/@pathList.mkString("/")" class="btn btn-danger">Cancel</a>
|
||||||
<input type="submit" id="commit" class="btn btn-success" value="Commit changes" disabled="true"/>
|
<input type="submit" id="commit" class="btn btn-success" value="Commit changes" disabled="true"/>
|
||||||
<input type="hidden" id="charset" name="charset" value="@content.charset"/>
|
<input type="hidden" id="charset" name="charset" value="@content.charset"/>
|
||||||
<input type="hidden" id="content" name="content" value=""/>
|
<input type="hidden" id="content" name="content" value=""/>
|
||||||
<input type="hidden" id="initial" value="@content.content.get"/>
|
<input type="hidden" id="initial" value="@content.content"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -74,7 +59,9 @@ $(function(){
|
|||||||
$('#editor').text($('#initial').val());
|
$('#editor').text($('#initial').val());
|
||||||
var editor = ace.edit("editor");
|
var editor = ace.edit("editor");
|
||||||
editor.setTheme("ace/theme/monokai");
|
editor.setTheme("ace/theme/monokai");
|
||||||
editor.getSession().setMode("ace/mode/@editorType(pathList.last)");
|
@if(fileName.isDefined){
|
||||||
|
editor.getSession().setMode("ace/mode/@editorType(fileName.get)");
|
||||||
|
}
|
||||||
editor.on('change', function(){
|
editor.on('change', function(){
|
||||||
$('#commit').attr('disabled', editor.getValue() == $('#initial').val());
|
$('#commit').attr('disabled', editor.getValue() == $('#initial').val());
|
||||||
});
|
});
|
||||||
@@ -82,5 +69,7 @@ $(function(){
|
|||||||
$('#commit').click(function(){
|
$('#commit').click(function(){
|
||||||
$('#content').val(editor.getValue());
|
$('#content').val(editor.getValue());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -19,6 +19,8 @@
|
|||||||
@pathList.zipWithIndex.map { case (section, i) =>
|
@pathList.zipWithIndex.map { case (section, i) =>
|
||||||
<a href="@url(repository)/tree/@encodeRefName(branch)/@pathList.take(i + 1).mkString("/")">@section</a> /
|
<a href="@url(repository)/tree/@encodeRefName(branch)/@pathList.take(i + 1).mkString("/")">@section</a> /
|
||||||
}
|
}
|
||||||
|
<!-- TODO Add new file icon for committers -->
|
||||||
|
<a href="@url(repository)/new/@encodeRefName(branch)/@pathList.mkString("/")">+</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<table class="table table-file-list" style="border: 1px solid silver;">
|
<table class="table table-file-list" style="border: 1px solid silver;">
|
||||||
|
|||||||
Reference in New Issue
Block a user