mirror of
				https://github.com/gitbucket/gitbucket.git
				synced 2025-10-31 02:25:59 +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 jp.sf.amateras.scalatra.forms._ | ||||
| import org.eclipse.jgit.dircache.DirCache | ||||
| import org.eclipse.jgit.revwalk.RevWalk | ||||
| import org.eclipse.jgit.revwalk.{RevCommit, RevWalk} | ||||
|  | ||||
| class RepositoryViewerController extends RepositoryViewerControllerBase | ||||
|   with RepositoryService with AccountService with ActivityService with ReferrerAuthenticator with CollaboratorsAuthenticator | ||||
| @@ -26,14 +26,40 @@ class RepositoryViewerController extends RepositoryViewerControllerBase | ||||
| trait RepositoryViewerControllerBase extends ControllerBase { | ||||
|   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( | ||||
|     "content" -> trim(label("Content", text())), | ||||
|     "message" -> trim(label("Messgae", optional(text()))), | ||||
|     "charset" -> trim(label("Charset", text())) | ||||
|     "branch"      -> trim(label("Branch", text(required))), | ||||
|     "path"        -> trim(label("Path", 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) | ||||
|  | ||||
|   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. | ||||
|    */ | ||||
| @@ -82,94 +108,66 @@ trait RepositoryViewerControllerBase extends ControllerBase { | ||||
|     } | ||||
|   }) | ||||
|  | ||||
|   /** | ||||
|    * Displays the file content of the specified branch or commit. | ||||
|    */ | ||||
|   get("/:owner/:repository/new/*")(collaboratorsOnly { repository => | ||||
|     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 => | ||||
|     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(id)) | ||||
|       val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(branch)) | ||||
|  | ||||
|       @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 => | ||||
|         // 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)) | ||||
|       getPathObjectId(git, path, revCommit).map { objectId => | ||||
|         val paths = path.split("/") | ||||
|         repo.html.editor(branch, repository, paths.take(paths.size - 1).toList, Some(paths.last), | ||||
|           JGitUtil.getContentInfo(git, path, objectId)) | ||||
|       } getOrElse NotFound | ||||
|     } | ||||
|   }) | ||||
|  | ||||
|   post("/:owner/:repository/edit/*", editorForm)(collaboratorsOnly { (form, repository) => | ||||
|     val (id, path) = splitPath(repository, multiParams("splat").head) | ||||
|  | ||||
|     LockUtil.lock(s"${repository.owner}/${repository.name}"){ | ||||
|   get("/:owner/:repository/remove/*")(collaboratorsOnly { repository => | ||||
|     val (branch, path) = splitPath(repository, multiParams("splat").head) | ||||
|     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/${id}" | ||||
|         val headTip  = git.getRepository.resolve(s"refs/heads/${id}") | ||||
|       val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(branch)) | ||||
|  | ||||
|         JGitUtil.processTree(git, headTip){ (treePath, tree) => | ||||
|           if(treePath != path){ | ||||
|             builder.add(JGitUtil.createDirCacheEntry(treePath, tree.getEntryFileMode, tree.getEntryObjectId)) | ||||
|           } | ||||
|       getPathObjectId(git, path, revCommit).map { objectId => | ||||
|         val paths = path.split("/") | ||||
|         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, | ||||
|           inserter.insert(Constants.OBJ_BLOB, form.content.getBytes(form.charset)))) | ||||
|         builder.finish() | ||||
|   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}")) | ||||
|  | ||||
|         val commitId = JGitUtil.createNewCommit(git, inserter, headTip, builder.getDirCache.writeTree(inserter), | ||||
|           loginAccount.fullName, loginAccount.mailAddress, form.message.getOrElse(s"Update ${path.split("/").last}")) | ||||
|     redirect(s"/${repository.owner}/${repository.name}/blob/${form.branch}/${ | ||||
|       if(form.path.length == 0) form.newFileName else s"${form.path}/${form.newFileName}" | ||||
|     }") | ||||
|   }) | ||||
|  | ||||
|         inserter.flush() | ||||
|         inserter.release() | ||||
|   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}") | ||||
|       }) | ||||
|  | ||||
|         // 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() | ||||
|     redirect(s"/${repository.owner}/${repository.name}/blob/${form.branch}/${ | ||||
|       if(form.path.length == 0) form.newFileName else s"${form.path}/${form.newFileName}" | ||||
|     }") | ||||
|   }) | ||||
|  | ||||
|         // record activity | ||||
|         recordPushActivity(repository.owner, repository.name, loginAccount.userName, id, | ||||
|           List(new CommitInfo(JGitUtil.getRevCommitFromId(git, commitId)))) | ||||
|   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}")) | ||||
|  | ||||
|         // TODO invoke hook | ||||
|  | ||||
|         redirect(s"/${repository.owner}/${repository.name}/blob/${id}/${path}") | ||||
|       } | ||||
|     } | ||||
|     redirect(s"/${repository.owner}/${repository.name}/tree/${form.branch}${if(form.path.length == 0) "" else form.path}") | ||||
|   }) | ||||
|  | ||||
|   /** | ||||
| @@ -181,19 +179,7 @@ trait RepositoryViewerControllerBase extends ControllerBase { | ||||
|  | ||||
|     using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git => | ||||
|       val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id)) | ||||
|  | ||||
|       @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 => | ||||
|       getPathObjectId(git, path, revCommit).map { objectId => | ||||
|         if(raw){ | ||||
|           // Download | ||||
|           defining(JGitUtil.getContentFromId(git, objectId, false).get){ bytes => | ||||
| @@ -201,26 +187,8 @@ trait RepositoryViewerControllerBase extends ControllerBase { | ||||
|             bytes | ||||
|           } | ||||
|         } else { | ||||
|           // 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.blob(id, repository, path.split("/").toList, content, new JGitUtil.CommitInfo(revCommit), | ||||
|             hasWritePermission(repository.owner, repository.name, context.loginAccount)) | ||||
|           repo.html.blob(id, repository, path.split("/").toList, JGitUtil.getContentInfo(git, path, objectId), | ||||
|             new JGitUtil.CommitInfo(revCommit), hasWritePermission(repository.owner, repository.name, context.loginAccount)) | ||||
|         } | ||||
|       } 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. | ||||
|    * | ||||
|   | ||||
| @@ -35,6 +35,9 @@ | ||||
|           } | ||||
|           <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> | ||||
|           @if(hasWritePermission){ | ||||
|             <a class="btn btn-mini btn-danger" href="@url(repository)/remove/@encodeRefName(branch)/@pathList.mkString("/")">Delete</a> | ||||
|           } | ||||
|         </div> | ||||
|       </th> | ||||
|     </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, | ||||
|   repository: service.RepositoryService.RepositoryInfo, | ||||
|   pathList: List[String], | ||||
|   content: util.JGitUtil.ContentInfo, | ||||
|   latestCommit: util.JGitUtil.CommitInfo)(implicit context: app.Context) | ||||
|   fileName: Option[String], | ||||
|   content: util.JGitUtil.ContentInfo)(implicit context: app.Context) | ||||
| @import context._ | ||||
| @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) | ||||
|   @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"> | ||||
|       <a href="@url(repository)/tree/@encodeRefName(branch)">@repository.name</a> / | ||||
|       @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> / | ||||
|       } | ||||
|     } | ||||
|       <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 { | ||||
| @@ -24,24 +26,7 @@ | ||||
|       height: 600px; | ||||
|     } | ||||
|     </style> | ||||
|   <form method="POST" action="@url(repository)/edit/@encodeRefName(branch)/@pathList.mkString("/")"> | ||||
|     <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> | ||||
|         <td> | ||||
|           <div id="editor"></div> | ||||
| @@ -55,14 +40,14 @@ | ||||
|           <strong>Commit changes</strong> | ||||
|         </div> | ||||
|         <div> | ||||
|           <input type="text" name="message" style="width: 98%;" placeholder="Update @pathList.last"/> | ||||
|           <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" disabled="true"/> | ||||
|           <input type="hidden" id="charset" name="charset" value="@content.charset"/> | ||||
|           <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> | ||||
| @@ -74,7 +59,9 @@ $(function(){ | ||||
|   $('#editor').text($('#initial').val()); | ||||
|   var editor = ace.edit("editor"); | ||||
|   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(){ | ||||
|     $('#commit').attr('disabled', editor.getValue() == $('#initial').val()); | ||||
|   }); | ||||
| @@ -82,5 +69,7 @@ $(function(){ | ||||
|   $('#commit').click(function(){ | ||||
|     $('#content').val(editor.getValue()); | ||||
|   }); | ||||
|  | ||||
|  | ||||
| }) | ||||
| </script> | ||||
|   | ||||
| @@ -19,6 +19,8 @@ | ||||
|     @pathList.zipWithIndex.map { case (section, i) => | ||||
|       <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 class="box"> | ||||
|     <table class="table table-file-list" style="border: 1px solid silver;"> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user