mirror of
				https://github.com/gitbucket/gitbucket.git
				synced 2025-10-31 10:36:05 +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,94 +108,66 @@ 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) | ||||||
|  |  | ||||||
|     LockUtil.lock(s"${repository.owner}/${repository.name}"){ |  | ||||||
|     using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git => |     using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git => | ||||||
|         val loginAccount = context.loginAccount.get |       val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(branch)) | ||||||
|         val builder  = DirCache.newInCore.builder() |  | ||||||
|         val inserter = git.getRepository.newObjectInserter() |  | ||||||
|         val headName = s"refs/heads/${id}" |  | ||||||
|         val headTip  = git.getRepository.resolve(s"refs/heads/${id}") |  | ||||||
|  |  | ||||||
|         JGitUtil.processTree(git, headTip){ (treePath, tree) => |       getPathObjectId(git, path, revCommit).map { objectId => | ||||||
|           if(treePath != path){ |         val paths = path.split("/") | ||||||
|             builder.add(JGitUtil.createDirCacheEntry(treePath, tree.getEntryFileMode, tree.getEntryObjectId)) |         repo.html.delete(branch, repository, paths.take(paths.size - 1).toList, paths.last, | ||||||
|           } |           JGitUtil.getContentInfo(git, path, objectId)) | ||||||
|  |       } getOrElse NotFound | ||||||
|     } |     } | ||||||
|  |   }) | ||||||
|  |  | ||||||
|         builder.add(JGitUtil.createDirCacheEntry(path, FileMode.REGULAR_FILE, |   post("/:owner/:repository/create", editorForm)(collaboratorsOnly { (form, repository) => | ||||||
|           inserter.insert(Constants.OBJ_BLOB, form.content.getBytes(form.charset)))) |     commitFile(repository, form.branch, form.path, Some(form.newFileName), None, form.content, form.charset, | ||||||
|         builder.finish() |       form.message.getOrElse(s"Create ${form.newFileName}")) | ||||||
|  |  | ||||||
|         val commitId = JGitUtil.createNewCommit(git, inserter, headTip, builder.getDirCache.writeTree(inserter), |     redirect(s"/${repository.owner}/${repository.name}/blob/${form.branch}/${ | ||||||
|           loginAccount.fullName, loginAccount.mailAddress, form.message.getOrElse(s"Update ${path.split("/").last}")) |       if(form.path.length == 0) form.newFileName else s"${form.path}/${form.newFileName}" | ||||||
|  |     }") | ||||||
|  |   }) | ||||||
|  |  | ||||||
|         inserter.flush() |   post("/:owner/:repository/update", editorForm)(collaboratorsOnly { (form, repository) => | ||||||
|         inserter.release() |     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}") | ||||||
|  |       }) | ||||||
|  |  | ||||||
|         // update refs |     redirect(s"/${repository.owner}/${repository.name}/blob/${form.branch}/${ | ||||||
|         val refUpdate = git.getRepository.updateRef(headName) |       if(form.path.length == 0) form.newFileName else s"${form.path}/${form.newFileName}" | ||||||
|         refUpdate.setNewObjectId(commitId) |     }") | ||||||
|         refUpdate.setForceUpdate(false) |   }) | ||||||
|         refUpdate.setRefLogIdent(new PersonIdent(loginAccount.fullName, loginAccount.mailAddress)) |  | ||||||
|         //refUpdate.setRefLogMessage("merged", true) |  | ||||||
|         refUpdate.update() |  | ||||||
|  |  | ||||||
|         // record activity |   post("/:owner/:repository/remove", deleteForm)(collaboratorsOnly { (form, repository) => | ||||||
|         recordPushActivity(repository.owner, repository.name, loginAccount.userName, id, |     commitFile(repository, form.branch, form.path, None, Some(form.fileName), "", "", | ||||||
|           List(new CommitInfo(JGitUtil.getRevCommitFromId(git, commitId)))) |       form.message.getOrElse(s"Delete ${form.fileName}")) | ||||||
|  |  | ||||||
|         // TODO invoke hook |     redirect(s"/${repository.owner}/${repository.name}/tree/${form.branch}${if(form.path.length == 0) "" else form.path}") | ||||||
|  |  | ||||||
|         redirect(s"/${repository.owner}/${repository.name}/blob/${id}/${path}") |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
| @@ -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,6 +35,9 @@ | |||||||
|           } |           } | ||||||
|           <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> | ||||||
|  |           @if(hasWritePermission){ | ||||||
|  |             <a class="btn btn-mini btn-danger" href="@url(repository)/remove/@encodeRefName(branch)/@pathList.mkString("/")">Delete</a> | ||||||
|  |           } | ||||||
|         </div> |         </div> | ||||||
|       </th> |       </th> | ||||||
|     </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,22 +1,24 @@ | |||||||
| @(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") | ||||||
|  |   <form method="POST" action="@url(repository)/@if(fileName.isEmpty){create}else{update}" validate="true"> | ||||||
|  |     <span class="error" id="error-newFileName"></span> | ||||||
|     <div class="head"> |     <div class="head"> | ||||||
|       <a href="@url(repository)/tree/@encodeRefName(branch)">@repository.name</a> / |       <a href="@url(repository)/tree/@encodeRefName(branch)">@repository.name</a> / | ||||||
|       @pathList.zipWithIndex.map { case (section, i) => |       @pathList.zipWithIndex.map { case (section, i) => | ||||||
|       @if(i == pathList.length - 1){ |  | ||||||
|         @section |  | ||||||
|       } 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> |     </div> | ||||||
|     <style type="text/css" media="screen"> |     <style type="text/css" media="screen"> | ||||||
|     #editor { |     #editor { | ||||||
| @@ -24,24 +26,7 @@ | |||||||
|       height: 600px; |       height: 600px; | ||||||
|     } |     } | ||||||
|     </style> |     </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