*/\r
define([\r
"jquery",\r
- "base64",\r
- "plugins/github/ui",\r
+ "github-api",\r
+ "plugins/modalbox",\r
"plugins/github/loginbox",\r
-], function($, Base64, UI, LoginBox) {\r
- // Load GitHub UI\r
+ "plugins/github/commentbox",\r
+ "utils",\r
+ "Markdown.Converter"\r
+], function($, GithubAPI) {\r
+ var GithubUser = function(login, password, repo, branch) {\r
+ this.login = login;\r
+ this.password = password;\r
+ this.repo = repo;\r
+ this.auth = "Basic " + (login + ':' + password).base64Encode();\r
+ this.branch = branch;\r
+ }\r
+\r
+ var GithubUI = {\r
+ init: function(upstream, basesha1) {\r
+ console.info("Github plugin: init GitHub module (upstream: "+ upstream +", base: " + basesha1 + ")");\r
+ this.origin = this._parseUpstream(upstream);\r
+ // Add github menu\r
+ $("nav.main ul").append(\r
+ $("<li/>")\r
+ .attr("id", "nitdoc-github-li")\r
+ .loginbox()\r
+ .loginbox("displayLogin")\r
+ .bind("loginbox_logoff", function() {\r
+ GithubUI.disactivate();\r
+ })\r
+ .bind("loginbox_login", function(event, infos) {\r
+ GithubUI._tryLoginFromCredentials(infos);\r
+ })\r
+ );\r
+ // check local session\r
+ this._tryLoginFromLocalSession();\r
+ },\r
+\r
+ activate: function(user, origin) {\r
+ this.openedComments = 0;\r
+ this._saveSession(user);\r
+ $("#nitdoc-github-li").loginbox("displayLogout", origin, user);\r
+ this._attachCommentBoxes();\r
+ this._reloadComments();\r
+\r
+ // Prevent page unload if there is comments in editing mode\r
+ $(window).on('beforeunload', function() {\r
+ if(GithubUI.openedComments > 0){\r
+ return "There is uncommited modified comments. Are you sure you want to leave this page?";\r
+ }\r
+ });\r
+ },\r
+\r
+ disactivate: function() {\r
+ if(this.openedComments > 0){\r
+ if(!confirm('There is uncommited modified comments. Are you sure you want to leave this page?')) {\r
+ return false;\r
+ }\r
+ }\r
+\r
+ localStorage.clear();\r
+ $("#nitdoc-github-li").loginbox("toggle");\r
+ $("#nitdoc-github-li").loginbox("displayLogin");\r
+ $(window).unbind('beforeunload');\r
+ //window.location.reload();\r
+ },\r
+\r
+ /* login */\r
+\r
+ _checkLoginInfos: function(infos) {\r
+ if(!infos.login || !infos.password || !infos.repo || !infos.branch) {\r
+ $("<p/>")\r
+ .text("Please enter your GitHub username, password, repository and branch.")\r
+ .modalbox({\r
+ title: "Sign in error",\r
+ isError: true\r
+ })\r
+ .modalbox("open");\r
+ return false;\r
+ } else {\r
+ return true;\r
+ }\r
+ },\r
+\r
+ _tryLoginFromCredentials: function(infos) {\r
+ if(this._checkLoginInfos(infos)) {\r
+ var isok = this._tryLogin(infos.login, infos.password, infos.repo, infos.branch);\r
+ if(isok === true) {\r
+ this.activate(this.user, this.origin);\r
+ } else {\r
+ if(isok == "error:login") {\r
+ $("<p/>")\r
+ .text("The username, password, repo or branch you entered is incorrect.")\r
+ .modalbox({\r
+ title: "Github sign in error",\r
+ isError: true\r
+ })\r
+ .modalbox("open");\r
+ } else if(isok == "error:sha") {\r
+ $("<p/>")\r
+ .text("The provided Github repository must contain the base commit '" + UI.origin.sha + "'.")\r
+ .modalbox({\r
+ title: "Github base commit error",\r
+ isError: true\r
+ })\r
+ .modalbox("open");\r
+ } else if(isok == "error:profile") {\r
+ $("<p/>")\r
+ .text("Please set your public name and email in your " +\r
+ "<a href='https://github.com/settings/profile'>GitHub profile</a>." +\r
+ "<br/><br/>Your public profile informations are used to sign-off your commits.")\r
+ .modalbox({\r
+ title: "Github profile error",\r
+ isError: true\r
+ })\r
+ .modalbox("open");\r
+ }\r
+ }\r
+ }\r
+ },\r
+\r
+ _tryLoginFromLocalSession: function() {\r
+ if(localStorage.user) {\r
+ var session = JSON.parse(localStorage.user);\r
+ var isok = this._tryLogin(\r
+ session.login,\r
+ session.password.base64Decode(),\r
+ session.repo,\r
+ session.branch\r
+ );\r
+ if(isok === true) {\r
+ this.activate(this.user, this.origin);\r
+ } else {\r
+ console.debug("Github plugin: Session found but authentification failed");\r
+ localStorage.clear();\r
+ }\r
+ } else {\r
+ console.debug("Github plugin: No session found");\r
+ }\r
+ },\r
+\r
+ _tryLogin: function(login, password, repo, branch) {\r
+ var tmpUser = new GithubUser(login, password, repo, branch);\r
+ if(!GithubAPI.login(tmpUser)) {\r
+ return "error:login";\r
+ }\r
+ if(!tmpUser.infos.name || !tmpUser.infos.email) {\r
+ return "error:profile";\r
+ }\r
+ var commit = GithubAPI.getCommit(tmpUser, this.origin.sha);\r
+ if(!commit || !commit.sha) {\r
+ return "error:sha";\r
+ }\r
+ this.user = tmpUser;\r
+ return true;\r
+ },\r
+\r
+ _saveSession: function(user) {\r
+ localStorage.user = JSON.stringify({\r
+ login: user.login,\r
+ password: user.password.base64Encode(),\r
+ repo: user.repo,\r
+ branch: user.branch,\r
+ });\r
+ // check local storage synchro with branch\r
+ if(localStorage.base != this.origin.sha) {\r
+ console.log("Base changed: cleaned cache");\r
+ localStorage.requests = "[]";\r
+ localStorage.base = this.origin.sha;\r
+ }\r
+ },\r
+\r
+ /* html decoration */\r
+\r
+ // Attach edit button on each comment\r
+ _attachCommentBoxes: function() {\r
+ $("textarea.baseComment").each(function() {\r
+ $(this).commentbox();\r
+\r
+ var isNew = false;\r
+ if(!$(this).val()) {\r
+ isNew = true;\r
+ $(this).nextAll(".info:first").find(".noComment").hide()\r
+ $(this).nextAll(".info:first").before(\r
+ $("<div/>")\r
+ .hide()\r
+ .addClass("comment")\r
+ .append(\r
+ $("<div/>").addClass("nitdoc")\r
+ )\r
+ )\r
+ }\r
+\r
+ $(this).nextAll(".info:first").prepend(\r
+ $("<a/>")\r
+ .addClass("nitdoc-github-editComment")\r
+ .css("cursor", "pointer")\r
+ .text((isNew ? "add" : "edit") + " comment")\r
+ .click($.proxy(GithubUI._openCommentBox, GithubUI, null, $(this)))\r
+ .after(" for ")\r
+ )\r
+\r
+ $(this).bind("commentbox_commit", function(event, data) {\r
+ GithubUI._saveChanges(data);\r
+ $(this).commentbox("close");\r
+ GithubUI._reloadComments();\r
+ })\r
+ .bind("commentbox_preview", function(event, data) {\r
+ var converter = new Markdown.Converter()\r
+ var html = converter.makeHtml(data.value);\r
+ $("<p/>")\r
+ .html(html)\r
+ .modalbox({\r
+ title: "Preview comment"\r
+ })\r
+ .modalbox("open");\r
+ })\r
+ .bind("commentbox_open", function(event, data) {\r
+ GithubUI.openedComments++;\r
+ $(this).nextAll(".comment").hide();\r
+ })\r
+ .bind("commentbox_close", function(event, data) {\r
+ GithubUI.openedComments--;\r
+ $(this).nextAll(".comment").show();\r
+ });\r
+ });\r
+ },\r
+\r
+ // reload comments from saved pull request\r
+ _reloadComments: function() {\r
+ if(!localStorage.requests){ return; }\r
+ $("p.pullRequest").remove();\r
+ var converter = new Markdown.Converter();\r
+ var requests = JSON.parse(localStorage.requests);\r
+ // Look for modified comments in page\r
+ for(i in requests) {\r
+ if(!requests[i]) { continue; }\r
+ var request = requests[i];\r
+ $("textarea[data-comment-location=\"" + request.location + "\"]").each(function () {\r
+ if(request.isClosed) {\r
+ var oldComment = request.oldComment.base64Decode();\r
+ var htmlComment = converter.makeHtml(oldComment);\r
+ $(this).val(oldComment);\r
+ if(!$(this).val()) {\r
+ $(this).nextAll("div.comment:first").hide();\r
+ } else {\r
+ $(this).nextAll("div.comment:first").show();\r
+ }\r
+ $(this).nextAll("div.comment").find("div.nitdoc").empty().html(htmlComment);\r
+ $(this).nextAll("p.info").find("a.nitdoc-github-editComment").show();\r
+ } else {\r
+ var newComment = request.comment.base64Decode();\r
+ var htmlComment = converter.makeHtml(newComment);\r
+ $(this).val(newComment);\r
+ if(!$(this).val()) {\r
+ $(this).nextAll("div.comment:first").hide();\r
+ } else {\r
+ $(this).nextAll("div.comment:first").show();\r
+ }\r
+ $(this).nextAll("div.comment").find("div.nitdoc").empty().html(htmlComment);\r
+ GithubUI._addPullRequestLink($(this), request);\r
+ $(this).nextAll("p.info").find("a.nitdoc-github-editComment").hide();\r
+ }\r
+ });\r
+ }\r
+ },\r
+\r
+ _addPullRequestLink: function(baseArea, request) {\r
+ baseArea.nextAll("p.info").before(\r
+ $("<p/>")\r
+ .addClass("pullRequest inheritance")\r
+ .text("comment modified in ")\r
+ .append(\r
+ $("<a/>")\r
+ .attr({\r
+ href: request.request.html_url,\r
+ title: "Review on GitHub"\r
+ })\r
+ .text("pull request #" + request.request.number)\r
+ )\r
+ .append(" ")\r
+ .append(\r
+ $("<a/>")\r
+ .data("pullrequest-number", request.request.number)\r
+ .addClass("nitdoc-github-update")\r
+ .text("update")\r
+ .click($.proxy(GithubUI._doUpdateRequest, GithubUI, null, baseArea, request))\r
+ )\r
+ .append(" ")\r
+ .append(\r
+ $("<a/>")\r
+ .data("pullrequest-number", request.request.number)\r
+ .addClass("nitdoc-github-cancel")\r
+ .text("cancel")\r
+ .click($.proxy(GithubUI._doCancelRequest, GithubUI, null, baseArea, request))\r
+ )\r
+ );\r
+ },\r
+\r
+ /* github calls */\r
+\r
+ _saveChanges: function(edit) {\r
+ // if pull request update close existing pull request for the comment\r
+ if(edit.requestID) {\r
+ this._closePullRequest(edit.requestID);\r
+ }\r
+ edit.oldContent = this._getFileContent(edit.location.path);\r
+ edit.newContent = this._mergeComment(edit.oldContent, edit.newComment, edit.location);\r
+ edit.request = this._pushChanges(edit)\r
+ if(!edit.request) {\r
+ $("<p/>")\r
+ .text("Unable to commit changes.<br/>" + response)\r
+ .modalbox({\r
+ title: "Github commit error",\r
+ isError: true\r
+ })\r
+ .modalbox("open");\r
+ return;\r
+ }\r
+ this._saveRequest(edit);\r
+ },\r
+\r
+ // save pull request in local storage\r
+ _saveRequest: function(edit) {\r
+ var requests = {};\r
+ if(localStorage.requests) {requests = JSON.parse(localStorage.requests)}\r
+ requests[edit.request.number] = {\r
+ request: edit.request,\r
+ location: edit.location.origin,\r
+ comment: edit.newComment.base64Encode(),\r
+ oldComment: edit.oldComment.base64Encode()\r
+ };\r
+ localStorage.requests = JSON.stringify(requests);\r
+ },\r
+\r
+ /*\r
+ Creating a new pull request with the new comment take 5 steps:\r
+ 1. get the base tree from latest commit\r
+\r
+ 2. create a new blob with updated file content\r
+ 3. post a new tree from base tree and blob\r
+ 4. post the new commit with new tree\r
+ 5. create the pull request\r
+ */\r
+ _pushChanges: function(edit) {\r
+ var baseTree = GithubAPI.getTree(this.user, this.origin.sha);\r
+ if(!baseTree.sha) {\r
+ $("<p/>")\r
+ .text("Unable to locate base tree.<br/>" + baseTree.status + ": " + baseTree.statusText)\r
+ .modalbox({\r
+ title: "Github commit error",\r
+ isError: true\r
+ })\r
+ .modalbox("open");\r
+ return false;\r
+ }\r
+ console.log("Base tree: " + baseTree.url);\r
+ var newBlob = GithubAPI.createBlob(this.user, edit.newContent);\r
+ if(!newBlob.sha) {\r
+ $("<p/>")\r
+ .text("Unable to create new blob.<br/>" + newBlob.status + ": " + newBlob.statusText)\r
+ .modalbox({\r
+ title: "Github commit error",\r
+ isError: true\r
+ })\r
+ .modalbox("open");\r
+ return false;\r
+ }\r
+ console.log("New blob: " + newBlob.url);\r
+ var newTree = GithubAPI.createTree(this.user, baseTree, edit.location.path, newBlob);\r
+ if(!newTree.sha) {\r
+ $("<p/>")\r
+ .text("Unable to create new tree.<br/>" + newTree.status + ": " + newTree.statusText)\r
+ .modalbox({\r
+ title: "Github commit error",\r
+ isError: true\r
+ })\r
+ .modalbox("open");\r
+ return false;\r
+ }\r
+ console.log("New tree: " + newTree.url);\r
+ var newCommit = GithubAPI.createCommit(this.user, edit.message, baseTree.sha, newTree);\r
+ if(!newCommit.sha) {\r
+ $("<p/>")\r
+ .text("Unable to create new commit.<br/>" + newCommit.status + ": " + newCommit.statusText)\r
+ .modalbox({\r
+ title: "Github commit error",\r
+ isError: true\r
+ })\r
+ .modalbox("open");\r
+ return false;\r
+ }\r
+ console.log("New commit: " + newCommit.url);\r
+ var pullRequest = GithubAPI.createPullRequest(this.user, edit.title, "Pull request from Nitdoc", this.origin, newCommit.sha);\r
+ if(!pullRequest.number) {\r
+ $("<p/>")\r
+ .text("Unable to create pull request.<br/>" + pullRequest.status + ": " + pullRequest.statusText)\r
+ .modalbox({\r
+ title: "Github commit error",\r
+ isError: true\r
+ })\r
+ .modalbox("open");\r
+ return false;\r
+ }\r
+ console.log("New pull request: " + pullRequest.url);\r
+ return pullRequest;\r
+ },\r
+\r
+ // close previously opened pull request\r
+ _closePullRequest: function(number) {\r
+ var requests = JSON.parse(localStorage.requests);\r
+ if(!requests[number]) {\r
+ $("<p/>")\r
+ .text("Unable to close pull request.<br/>" + "Pull request " + number + "not found")\r
+ .modalbox({\r
+ title: "Github commit error",\r
+ isError: true\r
+ })\r
+ .modalbox("open");\r
+ return false;\r
+ }\r
+ // close pull request\r
+ var res = GithubAPI.updatePullRequest(this.user, "Closed from Nitdoc", "", "closed", requests[number].request);\r
+ if(!res.id) {\r
+ $("<p/>")\r
+ .text("Unable to close pull request.<br/>" + res.status + ": " + res.statusText)\r
+ .modalbox({\r
+ title: "Github commit error",\r
+ isError: true\r
+ })\r
+ .modalbox("open");\r
+ return false;\r
+ }\r
+ // update in localstorage\r
+ requests[number].isClosed = true;\r
+ localStorage.requests = JSON.stringify(requests);\r
+ },\r
+\r
+ /* internals */\r
+\r
+ _parseUpstream: function(upstream) {\r
+ var parts = upstream.split(":");\r
+ return {\r
+ user: parts[0],\r
+ repo: parts[1],\r
+ branch: parts[2],\r
+ sha: basesha1\r
+ };\r
+ },\r
+\r
+ _getFileContent: function(githubUrl) {\r
+ var origFile = GithubAPI.getFile(this.user, githubUrl);\r
+ if(!origFile.content) {\r
+ $("<p/>")\r
+ .text("Unable to locate source file.<br/>" + origFile.status + ": " + origFile.statusText)\r
+ .modalbox({\r
+ title: "Github commit error",\r
+ isError: true\r
+ })\r
+ .modalbox("open");\r
+ return;\r
+ }\r
+ var base64Content = origFile.content.substring(0, origFile.content.length - 1)\r
+ return base64Content.base64Decode();\r
+ },\r
+\r
+ _mergeComment: function(fileContent, comment, location) {\r
+ // replace comment in file content\r
+ var res = new String();\r
+ var lines = fileContent.split("\n");\r
+ // copy lines fron 0 to lstart\r
+ for(var i = 0; i < location.lstart - 1; i++) {\r
+ res += lines[i] + "\n";\r
+ }\r
+ // set comment\r
+ if(comment && comment != "") {\r
+ var commentLines = comment.split("\n");\r
+ for(var i = 0; i < commentLines.length; i++) {\r
+ var line = commentLines[i];\r
+ var tab = location.tabpos > 1 ? "\t" : "";\r
+ res += tab + (line.length > 0 ? "# " : "#") + line + "\n";\r
+ }\r
+ }\r
+ // copy lines fron lend to end\r
+ for(var i = location.lend - 1; i < lines.length; i++) {\r
+ res += lines[i];\r
+ if(i < lines.length - 1) { res += "\n"; }\r
+ }\r
+ return res;\r
+ },\r
+\r
+ /* events */\r
+\r
+ _openCommentBox: function(event, baseArea) {\r
+ baseArea.commentbox("open", this.user);\r
+ },\r
+\r
+ _doCancelRequest: function(event, baseArea, request) {\r
+ this._closePullRequest(request.request.number);\r
+ this._reloadComments();\r
+ },\r
+\r
+ _doUpdateRequest: function(event, baseArea, request) {\r
+ baseArea.commentbox("open", this.user, request.request.number);\r
+ },\r
+ }\r
+\r
+ // Get github plugin data\r
var upstream = $("body").attr("data-github-upstream");\r
var basesha1 = $("body").attr("data-github-base-sha1");\r
if(upstream && basesha1) {\r
- console.log("init GitHub module (upstream: "+ upstream +", base: " + basesha1 + ")");\r
-\r
- // parse origin\r
- var parts = upstream.split(":");\r
- UI.origin = {\r
- user: parts[0],\r
- repo: parts[1],\r
- branch: parts[2],\r
- sha: basesha1\r
- };\r
-\r
- // check local session\r
- if(localStorage.user) {\r
-\r
- var session = JSON.parse(localStorage.user);\r
- UI.user = UI.tryLogin(session.login, Base64.decode(session.password), session.repo, session.branch);\r
- if(!UI.user.login) {\r
- console.log("Session found but authentification failed");\r
- localStorage.clear();\r
- }\r
-\r
- // activate ui\r
- LoginBox.init("nav.main ul");\r
- if(UI.user && UI.user.login) {\r
- LoginBox.displayLogout(UI.origin, UI.user);\r
- UI.activate(UI.user);\r
- } else {\r
- LoginBox.displayLogin();\r
- }\r
- } else {\r
- console.log("No session found");\r
- }\r
+ GithubUI.init(upstream, basesha1);\r
}\r
});\r