1 /* This file is part of NIT ( http://www.nitlanguage.org ).
3 Licensed under the Apache License, Version 2.0 (the "License");
4 you may not use this file except in compliance with the License.
5 You may obtain a copy of the License at
7 http://www.apache.org/licenses/LICENSE-2.0
9 Unless required by applicable law or agreed to in writing, software
10 distributed under the License is distributed on an "AS IS" BASIS,
11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 See the License for the specific language governing permissions and
13 limitations under the License.
15 Documentation generator for the nit language.
16 Generate API documentation in HTML format from nit source code.
20 * Nitdoc.Github comment edition module
22 * Allows user to modify source code comments directly from the Nitdoc
31 "plugins/github/loginbox",
32 "plugins/github/commentbox",
34 ], function($
, GithubAPI
, hljs
, marked
) {
35 var GithubUser
= function(login
, password
, repo
, branch
) {
37 this.password
= password
;
39 this.auth
= "Basic " + (login
+ ':' + password
).base64Encode();
44 init
: function(upstream
, basesha1
) {
45 console
.info("Github plugin: init GitHub module (upstream: "+ upstream
+", base: " + basesha1
+ ")");
46 this.origin
= this._parseUpstream(upstream
);
49 $
("nav.main ul").append(
51 .attr("id", "nitdoc-github-li")
53 .loginbox("displayLogin")
54 .bind("loginbox_logoff", function() {
55 GithubUI
.disactivate();
57 .bind("loginbox_login", function(event
, infos
) {
58 GithubUI
._tryLoginFromCredentials(infos
);
61 // check local session
62 this._tryLoginFromLocalSession();
65 activate
: function(user
, origin
) {
66 this.openedComments
= 0;
67 this._saveSession(user
);
68 $
("#nitdoc-github-li").loginbox("displayLogout", origin
, user
);
69 this._attachCommentBoxes();
70 this._reloadComments();
72 // Prevent page unload if there is comments in editing mode
73 $
(window
).on('beforeunload', function() {
74 if(GithubUI
.openedComments
> 0){
75 return "There is uncommited modified comments. Are you sure you want to leave this page?";
80 disactivate
: function() {
81 if(this.openedComments
> 0){
82 if(!confirm('There is uncommited modified comments. Are you sure you want to leave this page?')) {
88 $
("#nitdoc-github-li").loginbox("toggle");
89 $
("#nitdoc-github-li").loginbox("displayLogin");
90 $
(window
).unbind('beforeunload');
91 //window.location.reload();
96 _checkLoginInfos
: function(infos
) {
97 if(!infos
.login ||
!infos
.password ||
!infos
.repo ||
!infos
.branch
) {
99 .text("Please enter your GitHub username, password, repository and branch.")
101 title
: "Sign in error",
111 _tryLoginFromCredentials
: function(infos
) {
112 if(this._checkLoginInfos(infos
)) {
113 var isok
= this._tryLogin(infos
.login
, infos
.password
, infos
.repo
, infos
.branch
);
115 this.activate(this.user
, this.origin
);
117 if(isok
== "error:login") {
119 .text("The username, password, repo or branch you entered is incorrect.")
121 title
: "Github sign in error",
125 } else if(isok
== "error:sha") {
127 .text("The provided Github repository must contain the base commit '" + UI
.origin
.sha
+ "'.")
129 title
: "Github base commit error",
133 } else if(isok
== "error:profile") {
135 .text("Please set your public name and email in your " +
136 "<a href='https://github.com/settings/profile'>GitHub profile</a>." +
137 "<br/><br/>Your public profile informations are used to sign-off your commits.")
139 title
: "Github profile error",
148 _tryLoginFromLocalSession
: function() {
149 if(localStorage
.user
) {
150 var session
= JSON
.parse(localStorage
.user
);
151 var isok
= this._tryLogin(
153 session
.password
.base64Decode(),
158 this.activate(this.user
, this.origin
);
160 console
.debug("Github plugin: Session found but authentification failed");
161 localStorage
.clear();
164 console
.debug("Github plugin: No session found");
168 _tryLogin
: function(login
, password
, repo
, branch
) {
169 var tmpUser
= new GithubUser(login
, password
, repo
, branch
);
170 if(!GithubAPI
.login(tmpUser
)) {
171 return "error:login";
173 if(!tmpUser
.infos
.name ||
!tmpUser
.infos
.email
) {
174 return "error:profile";
176 var commit
= GithubAPI
.getCommit(tmpUser
, this.origin
.sha
);
177 if(!commit ||
!commit
.sha
) {
184 _saveSession
: function(user
) {
185 localStorage
.user
= JSON
.stringify({
187 password
: user
.password
.base64Encode(),
191 // check local storage synchro with branch
192 if(localStorage
.base
!= this.origin
.sha
) {
193 console
.log("Base changed: cleaned cache");
194 localStorage
.requests
= "[]";
195 localStorage
.base
= this.origin
.sha
;
199 /* html decoration */
201 // Attach edit button on each comment
202 _attachCommentBoxes
: function() {
203 $
("textarea.baseComment").each(function() {
204 $
(this).commentbox();
209 $
(this).nextAll(".info:first").find(".noComment").hide()
210 $
(this).nextAll(".info:first").before(
215 $
("<div/>").addClass("nitdoc")
220 $
(this).nextAll(".info:first").prepend(
222 .addClass("nitdoc-github-editComment")
223 .css("cursor", "pointer")
224 .text((isNew ?
"add" : "edit") + " comment")
225 .click($
.proxy(GithubUI
._openCommentBox
, GithubUI
, null, $
(this)))
229 $
(this).bind("commentbox_commit", function(event
, data
) {
230 GithubUI
._saveChanges(data
);
231 $
(this).commentbox("close");
232 GithubUI
._reloadComments();
234 .bind("commentbox_preview", function(event
, data
) {
236 .append($
("<h4/>").text("Comment:"))
239 .addClass("description")
246 .html(marked(data
.value
))
250 .append($
("<h4/>").text("Message:"))
253 .addClass("description")
258 $
("<div/>").html(marked(data
.message
))
263 title
: "Preview comment",
264 css
: {"min-width": "500px"}
268 .bind("commentbox_open", function(event
, data
) {
269 GithubUI
.openedComments
++;
270 $
(this).nextAll(".comment").hide();
272 .bind("commentbox_close", function(event
, data
) {
273 GithubUI
.openedComments
--;
274 $
(this).nextAll(".comment").show();
279 // reload comments from saved pull request
280 _reloadComments
: function() {
281 if(!localStorage
.requests
){ return; }
282 $
("p.pullRequest").remove();
283 var requests
= JSON
.parse(localStorage
.requests
);
284 // Look for modified comments in page
286 if(!requests
[i]) { continue; }
287 var request
= requests
[i];
288 $
("textarea[data-comment-location=\"" + request
.location
+ "\"]").each(function () {
289 if(request
.isClosed
) {
290 var oldComment
= request
.oldComment
.base64Decode();
291 var htmlComment
= marked(oldComment
);
292 $
(this).val(oldComment
);
294 $
(this).nextAll("div.comment:first").hide();
296 $
(this).nextAll("div.comment:first").show();
298 $
(this).nextAll("div.comment").find("div.nitdoc").empty().html(htmlComment
);
299 $
(this).nextAll("p.info").find("a.nitdoc-github-editComment").show();
301 var newComment
= request
.comment
.base64Decode();
302 var htmlComment
= marked(newComment
);
303 $
(this).val(newComment
);
305 $
(this).nextAll("div.comment:first").hide();
307 $
(this).nextAll("div.comment:first").show();
309 $
(this).nextAll("div.comment").find("div.nitdoc").empty().html(htmlComment
);
310 GithubUI
._addPullRequestLink($
(this), request
);
311 $
(this).nextAll("p.info").find("a.nitdoc-github-editComment").hide();
317 _addPullRequestLink
: function(baseArea
, request
) {
318 baseArea
.nextAll("p.info").before(
320 .addClass("pullRequest inheritance")
321 .text("comment modified in ")
325 href
: request
.request
.html_url
,
326 title
: "Review on GitHub"
328 .text("pull request #" + request
.request
.number
)
333 .data("pullrequest-number", request
.request
.number
)
334 .addClass("nitdoc-github-update")
336 .click($
.proxy(GithubUI
._doUpdateRequest
, GithubUI
, null, baseArea
, request
))
341 .data("pullrequest-number", request
.request
.number
)
342 .addClass("nitdoc-github-cancel")
344 .click($
.proxy(GithubUI
._doCancelRequest
, GithubUI
, null, baseArea
, request
))
351 _saveChanges
: function(edit
) {
352 // if pull request update close existing pull request for the comment
354 this._closePullRequest(edit
.requestID
);
356 edit
.oldContent
= this._getFileContent(edit
.location
.path
);
357 edit
.newContent
= this._mergeComment(edit
.oldContent
, edit
.newComment
, edit
.location
);
358 edit
.request
= this._pushChanges(edit
)
361 .text("Unable to commit changes.<br/>" + response
)
363 title
: "Github commit error",
369 this._saveRequest(edit
);
372 // save pull request in local storage
373 _saveRequest
: function(edit
) {
375 if(localStorage
.requests
) {requests
= JSON
.parse(localStorage
.requests
)}
376 requests
[edit
.request
.number
] = {
377 request
: edit
.request
,
378 location
: edit
.location
.origin
,
379 comment
: edit
.newComment
.base64Encode(),
380 oldComment
: edit
.oldComment
.base64Encode()
382 localStorage
.requests
= JSON
.stringify(requests
);
386 Creating a new pull request with the new comment take 5 steps:
387 1. get the base tree from latest commit
389 2. create a new blob with updated file content
390 3. post a new tree from base tree and blob
391 4. post the new commit with new tree
392 5. create the pull request
394 _pushChanges
: function(edit
) {
395 var baseTree
= GithubAPI
.getTree(this.user
, this.origin
.sha
);
398 .text("Unable to locate base tree.<br/>" + baseTree
.status
+ ": " + baseTree
.statusText
)
400 title
: "Github commit error",
406 console
.log("Base tree: " + baseTree
.url
);
407 var newBlob
= GithubAPI
.createBlob(this.user
, edit
.newContent
);
410 .text("Unable to create new blob.<br/>" + newBlob
.status
+ ": " + newBlob
.statusText
)
412 title
: "Github commit error",
418 console
.log("New blob: " + newBlob
.url
);
419 var newTree
= GithubAPI
.createTree(this.user
, baseTree
, edit
.location
.path
, newBlob
);
422 .text("Unable to create new tree.<br/>" + newTree
.status
+ ": " + newTree
.statusText
)
424 title
: "Github commit error",
430 console
.log("New tree: " + newTree
.url
);
431 var newCommit
= GithubAPI
.createCommit(this.user
, edit
.message
, baseTree
.sha
, newTree
);
434 .text("Unable to create new commit.<br/>" + newCommit
.status
+ ": " + newCommit
.statusText
)
436 title
: "Github commit error",
442 console
.log("New commit: " + newCommit
.url
);
443 var pullRequest
= GithubAPI
.createPullRequest(this.user
, edit
.title
, "Pull request from Nitdoc", this.origin
, newCommit
.sha
);
444 if(!pullRequest
.number
) {
446 .text("Unable to create pull request.<br/>" + pullRequest
.status
+ ": " + pullRequest
.statusText
)
448 title
: "Github commit error",
454 console
.log("New pull request: " + pullRequest
.url
);
458 // close previously opened pull request
459 _closePullRequest
: function(number
) {
460 var requests
= JSON
.parse(localStorage
.requests
);
461 if(!requests
[number]) {
463 .text("Unable to close pull request.<br/>" + "Pull request " + number
+ "not found")
465 title
: "Github commit error",
471 // close pull request
472 var res
= GithubAPI
.updatePullRequest(this.user
, "Closed from Nitdoc", "", "closed", requests
[number].request
);
475 .text("Unable to close pull request.<br/>" + res
.status
+ ": " + res
.statusText
)
477 title
: "Github commit error",
483 // update in localstorage
484 requests
[number].isClosed
= true;
485 localStorage
.requests
= JSON
.stringify(requests
);
490 _initMarked
: function() {
491 var renderer
= new marked
.Renderer();
492 renderer
.code
= function(code
) {
493 return '<pre class="nitcode hljs">' + hljs
.highlight('nit', code
).value
+ '</pre>';
495 renderer
.codespan
= function(code
) {
496 return '<code class="nitcode hljs">' + hljs
.highlight('nit', code
).value
+ '</code>';
510 _parseUpstream
: function(upstream
) {
511 var parts
= upstream
.split(":");
520 _getFileContent
: function(githubUrl
) {
521 var origFile
= GithubAPI
.getFile(this.user
, githubUrl
);
522 if(!origFile
.content
) {
524 .text("Unable to locate source file.<br/>" + origFile
.status
+ ": " + origFile
.statusText
)
526 title
: "Github commit error",
532 var base64Content
= origFile
.content
.substring(0, origFile
.content
.length
- 1)
533 return base64Content
.base64Decode();
536 _mergeComment
: function(fileContent
, comment
, location
) {
537 // replace comment in file content
538 var res
= new String();
539 var lines
= fileContent
.split("\n");
540 // copy lines fron 0 to lstart
541 for(var i
= 0; i
< location
.lstart
- 1; i
++) {
542 res
+= lines
[i] + "\n";
545 if(comment
&& comment
!= "") {
546 var commentLines
= comment
.split("\n");
547 for(var i
= 0; i
< commentLines
.length
; i
++) {
548 var line
= commentLines
[i];
549 var tab
= location
.tabpos
> 1 ?
"\t" : "";
550 res
+= tab
+ (line
.length
> 0 ?
"# " : "#") + line
+ "\n";
553 // copy lines fron lend to end
554 for(var i
= location
.lend
- 1; i
< lines
.length
; i
++) {
556 if(i
< lines
.length
- 1) { res
+= "\n"; }
563 _openCommentBox
: function(event
, baseArea
) {
564 baseArea
.commentbox("open", this.user
);
567 _doCancelRequest
: function(event
, baseArea
, request
) {
568 this._closePullRequest(request
.request
.number
);
569 this._reloadComments();
572 _doUpdateRequest
: function(event
, baseArea
, request
) {
573 baseArea
.commentbox("open", this.user
, request
.request
.number
);
577 // Get github plugin data
578 var upstream
= $
("body").attr("data-github-upstream");
579 var basesha1
= $
("body").attr("data-github-base-sha1");
580 if(upstream
&& basesha1
) {
581 GithubUI
.init(upstream
, basesha1
);