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 $
("#topmenu>.container-fluid").append(
52 "id": "nitdoc-github-li",
54 "class": "navbar-btn navbar-right btn-link",
56 "data-container": "body",
57 "data-toggle": "popover",
58 "data-placement": "bottom",
59 "data-content": "bottom",
63 //.loginbox("displayLogin")
64 .bind("loginbox_logoff", function() {
65 GithubUI
.disactivate();
67 .bind("loginbox_login", function(event
, infos
) {
68 GithubUI
._tryLoginFromCredentials(infos
);
71 // check local session
72 this._tryLoginFromLocalSession();
75 activate
: function(user
, origin
) {
76 this.openedComments
= 0;
77 this._saveSession(user
);
78 $
("#nitdoc-github-li").loginbox("displayLogout", origin
, user
);
79 this._attachCommentBoxes();
80 this._reloadComments();
82 // Prevent page unload if there is comments in editing mode
83 $
(window
).on('beforeunload', function() {
84 if(GithubUI
.openedComments
> 0){
85 return "There is uncommited modified comments. Are you sure you want to leave this page?";
90 disactivate
: function() {
91 if(this.openedComments
> 0){
92 if(!confirm('There is uncommited modified comments. Are you sure you want to leave this page?')) {
98 $
("#nitdoc-github-li").loginbox("toggle");
99 $
("#nitdoc-github-li").loginbox("displayLogin");
100 $
(window
).unbind('beforeunload');
101 //window.location.reload();
106 _checkLoginInfos
: function(infos
) {
107 if(!infos
.login ||
!infos
.password ||
!infos
.repo ||
!infos
.branch
) {
109 .text("Please enter your GitHub username, password, repository and branch.")
111 title
: "Sign in error",
121 _tryLoginFromCredentials
: function(infos
) {
122 if(this._checkLoginInfos(infos
)) {
123 var isok
= this._tryLogin(infos
.login
, infos
.password
, infos
.repo
, infos
.branch
);
125 this.activate(this.user
, this.origin
);
127 if(isok
== "error:login") {
129 .text("The username, password, repo or branch you entered is incorrect.")
131 title
: "Github sign in error",
135 } else if(isok
== "error:sha") {
137 .text("The provided Github repository must contain the base commit '" + this.origin
.sha
+ "'.")
139 title
: "Github base commit error",
143 } else if(isok
== "error:profile") {
145 .text("Please set your public name and email in your " +
146 "<a href='https://github.com/settings/profile'>GitHub profile</a>." +
147 "<br/><br/>Your public profile informations are used to sign-off your commits.")
149 title
: "Github profile error",
158 _tryLoginFromLocalSession
: function() {
159 if(localStorage
.user
) {
160 var session
= JSON
.parse(localStorage
.user
);
161 var isok
= this._tryLogin(
163 session
.password
.base64Decode(),
168 this.activate(this.user
, this.origin
);
170 console
.debug("Github plugin: Session found but authentification failed");
171 localStorage
.clear();
174 console
.debug("Github plugin: No session found");
178 _tryLogin
: function(login
, password
, repo
, branch
) {
179 var tmpUser
= new GithubUser(login
, password
, repo
, branch
);
180 if(!GithubAPI
.login(tmpUser
)) {
181 return "error:login";
183 if(!tmpUser
.infos
.name ||
!tmpUser
.infos
.email
) {
184 return "error:profile";
186 var commit
= GithubAPI
.getCommit(tmpUser
, this.origin
.sha
);
187 if(!commit ||
!commit
.sha
) {
194 _saveSession
: function(user
) {
195 localStorage
.user
= JSON
.stringify({
197 password
: user
.password
.base64Encode(),
201 // check local storage synchro with branch
202 if(localStorage
.base
!= this.origin
.sha
) {
203 console
.log("Base changed: cleaned cache");
204 localStorage
.requests
= "[]";
205 localStorage
.base
= this.origin
.sha
;
209 /* html decoration */
211 // Attach edit button on each comment
212 _attachCommentBoxes
: function() {
213 $
("textarea.baseComment").each(function() {
214 $
(this).commentbox();
219 $
(this).nextAll(".info:first").find(".noComment").hide()
220 $
(this).nextAll(".info:first").before(
225 $
("<div/>").addClass("nitdoc")
230 $
(this).nextAll(".info:first").prepend(
232 .addClass("nitdoc-github-editComment")
233 .css("cursor", "pointer")
234 .text((isNew ?
"add" : "edit") + " comment")
235 .click($
.proxy(GithubUI
._openCommentBox
, GithubUI
, null, $
(this)))
239 $
(this).bind("commentbox_commit", function(event
, data
) {
240 GithubUI
._saveChanges(data
);
241 $
(this).commentbox("close");
242 GithubUI
._reloadComments();
244 .bind("commentbox_preview", function(event
, data
) {
246 .append($
("<h4/>").text("Comment:"))
249 .addClass("description")
256 .html(marked(data
.value
))
260 .append($
("<h4/>").text("Message:"))
263 .addClass("description")
268 $
("<div/>").html(marked(data
.message
))
273 title
: "Preview comment",
274 css
: {"min-width": "500px"}
278 .bind("commentbox_open", function(event
, data
) {
279 GithubUI
.openedComments
++;
280 $
(this).nextAll(".comment").hide();
282 .bind("commentbox_close", function(event
, data
) {
283 GithubUI
.openedComments
--;
284 $
(this).nextAll(".comment").show();
289 // reload comments from saved pull request
290 _reloadComments
: function() {
291 if(!localStorage
.requests
){ return; }
292 $
("p.pullRequest").remove();
293 var requests
= JSON
.parse(localStorage
.requests
);
294 // Look for modified comments in page
296 if(!requests
[i]) { continue; }
297 var request
= requests
[i];
298 $
("textarea[data-comment-location=\"" + request
.location
+ "\"]").each(function () {
299 if(request
.isClosed
) {
300 var oldComment
= request
.oldComment
.base64Decode();
301 var htmlComment
= marked(oldComment
);
302 $
(this).val(oldComment
);
304 $
(this).nextAll("div.comment:first").hide();
306 $
(this).nextAll("div.comment:first").show();
308 $
(this).nextAll("div.comment").find("div.nitdoc").empty().html(htmlComment
);
309 $
(this).nextAll("p.info").find("a.nitdoc-github-editComment").show();
311 var newComment
= request
.comment
.base64Decode();
312 var htmlComment
= marked(newComment
);
313 $
(this).val(newComment
);
315 $
(this).nextAll("div.comment:first").hide();
317 $
(this).nextAll("div.comment:first").show();
319 $
(this).nextAll("div.comment").find("div.nitdoc").empty().html(htmlComment
);
320 GithubUI
._addPullRequestLink($
(this), request
);
321 $
(this).nextAll("p.info").find("a.nitdoc-github-editComment").hide();
327 _addPullRequestLink
: function(baseArea
, request
) {
328 baseArea
.nextAll("p.info").before(
330 .addClass("pullRequest inheritance")
331 .text("comment modified in ")
335 href
: request
.request
.html_url
,
336 title
: "Review on GitHub"
338 .text("pull request #" + request
.request
.number
)
343 .data("pullrequest-number", request
.request
.number
)
344 .addClass("nitdoc-github-update")
346 .click($
.proxy(GithubUI
._doUpdateRequest
, GithubUI
, null, baseArea
, request
))
351 .data("pullrequest-number", request
.request
.number
)
352 .addClass("nitdoc-github-cancel")
354 .click($
.proxy(GithubUI
._doCancelRequest
, GithubUI
, null, baseArea
, request
))
361 _saveChanges
: function(edit
) {
362 // if pull request update close existing pull request for the comment
364 this._closePullRequest(edit
.requestID
);
366 edit
.oldContent
= this._getFileContent(edit
.location
.path
);
367 edit
.newContent
= this._mergeComment(edit
.oldContent
, edit
.newComment
, edit
.location
);
368 edit
.request
= this._pushChanges(edit
)
371 .text("Unable to commit changes.<br/>" + response
)
373 title
: "Github commit error",
379 this._saveRequest(edit
);
382 // save pull request in local storage
383 _saveRequest
: function(edit
) {
385 if(localStorage
.requests
) {requests
= JSON
.parse(localStorage
.requests
)}
386 requests
[edit
.request
.number
] = {
387 request
: edit
.request
,
388 location
: edit
.location
.origin
,
389 comment
: edit
.newComment
.base64Encode(),
390 oldComment
: edit
.oldComment
.base64Encode()
392 localStorage
.requests
= JSON
.stringify(requests
);
396 Creating a new pull request with the new comment take 5 steps:
397 1. get the base tree from latest commit
399 2. create a new blob with updated file content
400 3. post a new tree from base tree and blob
401 4. post the new commit with new tree
402 5. create the pull request
404 _pushChanges
: function(edit
) {
405 var baseTree
= GithubAPI
.getTree(this.user
, this.origin
.sha
);
408 .text("Unable to locate base tree.<br/>" + baseTree
.status
+ ": " + baseTree
.statusText
)
410 title
: "Github commit error",
416 console
.log("Base tree: " + baseTree
.url
);
417 var newBlob
= GithubAPI
.createBlob(this.user
, edit
.newContent
);
420 .text("Unable to create new blob.<br/>" + newBlob
.status
+ ": " + newBlob
.statusText
)
422 title
: "Github commit error",
428 console
.log("New blob: " + newBlob
.url
);
429 var newTree
= GithubAPI
.createTree(this.user
, baseTree
, edit
.location
.path
, newBlob
);
432 .text("Unable to create new tree.<br/>" + newTree
.status
+ ": " + newTree
.statusText
)
434 title
: "Github commit error",
440 console
.log("New tree: " + newTree
.url
);
441 var newCommit
= GithubAPI
.createCommit(this.user
, edit
.message
, baseTree
.sha
, newTree
);
444 .text("Unable to create new commit.<br/>" + newCommit
.status
+ ": " + newCommit
.statusText
)
446 title
: "Github commit error",
452 console
.log("New commit: " + newCommit
.url
);
453 var pullRequest
= GithubAPI
.createPullRequest(this.user
, edit
.title
, "Pull request from Nitdoc", this.origin
, newCommit
.sha
);
454 if(!pullRequest
.number
) {
456 .text("Unable to create pull request.<br/>" + pullRequest
.status
+ ": " + pullRequest
.statusText
)
458 title
: "Github commit error",
464 console
.log("New pull request: " + pullRequest
.url
);
468 // close previously opened pull request
469 _closePullRequest
: function(number
) {
470 var requests
= JSON
.parse(localStorage
.requests
);
471 if(!requests
[number]) {
473 .text("Unable to close pull request.<br/>" + "Pull request " + number
+ "not found")
475 title
: "Github commit error",
481 // close pull request
482 var res
= GithubAPI
.updatePullRequest(this.user
, "Closed from Nitdoc", "", "closed", requests
[number].request
);
485 .text("Unable to close pull request.<br/>" + res
.status
+ ": " + res
.statusText
)
487 title
: "Github commit error",
493 // update in localstorage
494 requests
[number].isClosed
= true;
495 localStorage
.requests
= JSON
.stringify(requests
);
500 _initMarked
: function() {
501 var renderer
= new marked
.Renderer();
502 renderer
.code
= function(code
) {
503 return '<pre class="nitcode hljs">' + hljs
.highlight('nit', code
).value
+ '</pre>';
505 renderer
.codespan
= function(code
) {
506 return '<code class="nitcode hljs">' + hljs
.highlight('nit', code
).value
+ '</code>';
520 _parseUpstream
: function(upstream
) {
521 var parts
= upstream
.split(":");
530 _getFileContent
: function(githubUrl
) {
531 var origFile
= GithubAPI
.getFile(this.user
, githubUrl
);
532 if(!origFile
.content
) {
534 .text("Unable to locate source file.<br/>" + origFile
.status
+ ": " + origFile
.statusText
)
536 title
: "Github commit error",
542 var base64Content
= origFile
.content
.substring(0, origFile
.content
.length
- 1)
543 return base64Content
.base64Decode();
546 _mergeComment
: function(fileContent
, comment
, location
) {
547 // replace comment in file content
548 var res
= new String();
549 var lines
= fileContent
.split("\n");
550 // copy lines fron 0 to lstart
551 for(var i
= 0; i
< location
.lstart
- 1; i
++) {
552 res
+= lines
[i] + "\n";
555 if(comment
&& comment
!= "") {
556 var commentLines
= comment
.split("\n");
557 for(var i
= 0; i
< commentLines
.length
; i
++) {
558 var line
= commentLines
[i];
559 var tab
= location
.tabpos
> 1 ?
"\t" : "";
560 res
+= tab
+ (line
.length
> 0 ?
"# " : "#") + line
+ "\n";
563 // copy lines fron lend to end
564 for(var i
= location
.lend
- 1; i
< lines
.length
; i
++) {
566 if(i
< lines
.length
- 1) { res
+= "\n"; }
573 _openCommentBox
: function(event
, baseArea
) {
574 baseArea
.commentbox("open", this.user
);
577 _doCancelRequest
: function(event
, baseArea
, request
) {
578 this._closePullRequest(request
.request
.number
);
579 this._reloadComments();
582 _doUpdateRequest
: function(event
, baseArea
, request
) {
583 baseArea
.commentbox("open", this.user
, request
.request
.number
);
587 // Get github plugin data
588 var upstream
= $
("body").attr("data-github-upstream");
589 var basesha1
= $
("body").attr("data-github-base-sha1");
590 if(upstream
&& basesha1
) {
591 GithubUI
.init(upstream
, basesha1
);