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
28 "plugins/github/loginbox",
29 "plugins/github/commentbox",
32 ], function($
, GithubAPI
) {
33 var GithubUser
= function(login
, password
, repo
, branch
) {
35 this.password
= password
;
37 this.auth
= "Basic " + (login
+ ':' + password
).base64Encode();
42 init
: function(upstream
, basesha1
) {
43 console
.info("Github plugin: init GitHub module (upstream: "+ upstream
+", base: " + basesha1
+ ")");
44 this.origin
= this._parseUpstream(upstream
);
46 $
("nav.main ul").append(
48 .attr("id", "nitdoc-github-li")
50 .loginbox("displayLogin")
51 .bind("loginbox_logoff", function() {
52 GithubUI
.disactivate();
54 .bind("loginbox_login", function(event
, infos
) {
55 GithubUI
._tryLoginFromCredentials(infos
);
58 // check local session
59 this._tryLoginFromLocalSession();
62 activate
: function(user
, origin
) {
63 this.openedComments
= 0;
64 this._saveSession(user
);
65 $
("#nitdoc-github-li").loginbox("displayLogout", origin
, user
);
66 this._attachCommentBoxes();
67 this._reloadComments();
69 // Prevent page unload if there is comments in editing mode
70 $
(window
).on('beforeunload', function() {
71 if(GithubUI
.openedComments
> 0){
72 return "There is uncommited modified comments. Are you sure you want to leave this page?";
77 disactivate
: function() {
78 if(this.openedComments
> 0){
79 if(!confirm('There is uncommited modified comments. Are you sure you want to leave this page?')) {
85 $
("#nitdoc-github-li").loginbox("toggle");
86 $
("#nitdoc-github-li").loginbox("displayLogin");
87 $
(window
).unbind('beforeunload');
88 //window.location.reload();
93 _checkLoginInfos
: function(infos
) {
94 if(!infos
.login ||
!infos
.password ||
!infos
.repo ||
!infos
.branch
) {
96 .text("Please enter your GitHub username, password, repository and branch.")
98 title
: "Sign in error",
108 _tryLoginFromCredentials
: function(infos
) {
109 if(this._checkLoginInfos(infos
)) {
110 var isok
= this._tryLogin(infos
.login
, infos
.password
, infos
.repo
, infos
.branch
);
112 this.activate(this.user
, this.origin
);
114 if(isok
== "error:login") {
116 .text("The username, password, repo or branch you entered is incorrect.")
118 title
: "Github sign in error",
122 } else if(isok
== "error:sha") {
124 .text("The provided Github repository must contain the base commit '" + UI
.origin
.sha
+ "'.")
126 title
: "Github base commit error",
130 } else if(isok
== "error:profile") {
132 .text("Please set your public name and email in your " +
133 "<a href='https://github.com/settings/profile'>GitHub profile</a>." +
134 "<br/><br/>Your public profile informations are used to sign-off your commits.")
136 title
: "Github profile error",
145 _tryLoginFromLocalSession
: function() {
146 if(localStorage
.user
) {
147 var session
= JSON
.parse(localStorage
.user
);
148 var isok
= this._tryLogin(
150 session
.password
.base64Decode(),
155 this.activate(this.user
, this.origin
);
157 console
.debug("Github plugin: Session found but authentification failed");
158 localStorage
.clear();
161 console
.debug("Github plugin: No session found");
165 _tryLogin
: function(login
, password
, repo
, branch
) {
166 var tmpUser
= new GithubUser(login
, password
, repo
, branch
);
167 if(!GithubAPI
.login(tmpUser
)) {
168 return "error:login";
170 if(!tmpUser
.infos
.name ||
!tmpUser
.infos
.email
) {
171 return "error:profile";
173 var commit
= GithubAPI
.getCommit(tmpUser
, this.origin
.sha
);
174 if(!commit ||
!commit
.sha
) {
181 _saveSession
: function(user
) {
182 localStorage
.user
= JSON
.stringify({
184 password
: user
.password
.base64Encode(),
188 // check local storage synchro with branch
189 if(localStorage
.base
!= this.origin
.sha
) {
190 console
.log("Base changed: cleaned cache");
191 localStorage
.requests
= "[]";
192 localStorage
.base
= this.origin
.sha
;
196 /* html decoration */
198 // Attach edit button on each comment
199 _attachCommentBoxes
: function() {
200 $
("textarea.baseComment").each(function() {
201 $
(this).commentbox();
206 $
(this).nextAll(".info:first").find(".noComment").hide()
207 $
(this).nextAll(".info:first").before(
212 $
("<div/>").addClass("nitdoc")
217 $
(this).nextAll(".info:first").prepend(
219 .addClass("nitdoc-github-editComment")
220 .css("cursor", "pointer")
221 .text((isNew ?
"add" : "edit") + " comment")
222 .click($
.proxy(GithubUI
._openCommentBox
, GithubUI
, null, $
(this)))
226 $
(this).bind("commentbox_commit", function(event
, data
) {
227 GithubUI
._saveChanges(data
);
228 $
(this).commentbox("close");
229 GithubUI
._reloadComments();
231 .bind("commentbox_preview", function(event
, data
) {
232 var converter
= new Markdown
.Converter()
233 var html
= converter
.makeHtml(data
.value
);
237 title
: "Preview comment"
241 .bind("commentbox_open", function(event
, data
) {
242 GithubUI
.openedComments
++;
243 $
(this).nextAll(".comment").hide();
245 .bind("commentbox_close", function(event
, data
) {
246 GithubUI
.openedComments
--;
247 $
(this).nextAll(".comment").show();
252 // reload comments from saved pull request
253 _reloadComments
: function() {
254 if(!localStorage
.requests
){ return; }
255 $
("p.pullRequest").remove();
256 var converter
= new Markdown
.Converter();
257 var requests
= JSON
.parse(localStorage
.requests
);
258 // Look for modified comments in page
260 if(!requests
[i]) { continue; }
261 var request
= requests
[i];
262 $
("textarea[data-comment-location=\"" + request
.location
+ "\"]").each(function () {
263 if(request
.isClosed
) {
264 var oldComment
= request
.oldComment
.base64Decode();
265 var htmlComment
= converter
.makeHtml(oldComment
);
266 $
(this).val(oldComment
);
268 $
(this).nextAll("div.comment:first").hide();
270 $
(this).nextAll("div.comment:first").show();
272 $
(this).nextAll("div.comment").find("div.nitdoc").empty().html(htmlComment
);
273 $
(this).nextAll("p.info").find("a.nitdoc-github-editComment").show();
275 var newComment
= request
.comment
.base64Decode();
276 var htmlComment
= converter
.makeHtml(newComment
);
277 $
(this).val(newComment
);
279 $
(this).nextAll("div.comment:first").hide();
281 $
(this).nextAll("div.comment:first").show();
283 $
(this).nextAll("div.comment").find("div.nitdoc").empty().html(htmlComment
);
284 GithubUI
._addPullRequestLink($
(this), request
);
285 $
(this).nextAll("p.info").find("a.nitdoc-github-editComment").hide();
291 _addPullRequestLink
: function(baseArea
, request
) {
292 baseArea
.nextAll("p.info").before(
294 .addClass("pullRequest inheritance")
295 .text("comment modified in ")
299 href
: request
.request
.html_url
,
300 title
: "Review on GitHub"
302 .text("pull request #" + request
.request
.number
)
307 .data("pullrequest-number", request
.request
.number
)
308 .addClass("nitdoc-github-update")
310 .click($
.proxy(GithubUI
._doUpdateRequest
, GithubUI
, null, baseArea
, request
))
315 .data("pullrequest-number", request
.request
.number
)
316 .addClass("nitdoc-github-cancel")
318 .click($
.proxy(GithubUI
._doCancelRequest
, GithubUI
, null, baseArea
, request
))
325 _saveChanges
: function(edit
) {
326 // if pull request update close existing pull request for the comment
328 this._closePullRequest(edit
.requestID
);
330 edit
.oldContent
= this._getFileContent(edit
.location
.path
);
331 edit
.newContent
= this._mergeComment(edit
.oldContent
, edit
.newComment
, edit
.location
);
332 edit
.request
= this._pushChanges(edit
)
335 .text("Unable to commit changes.<br/>" + response
)
337 title
: "Github commit error",
343 this._saveRequest(edit
);
346 // save pull request in local storage
347 _saveRequest
: function(edit
) {
349 if(localStorage
.requests
) {requests
= JSON
.parse(localStorage
.requests
)}
350 requests
[edit
.request
.number
] = {
351 request
: edit
.request
,
352 location
: edit
.location
.origin
,
353 comment
: edit
.newComment
.base64Encode(),
354 oldComment
: edit
.oldComment
.base64Encode()
356 localStorage
.requests
= JSON
.stringify(requests
);
360 Creating a new pull request with the new comment take 5 steps:
361 1. get the base tree from latest commit
363 2. create a new blob with updated file content
364 3. post a new tree from base tree and blob
365 4. post the new commit with new tree
366 5. create the pull request
368 _pushChanges
: function(edit
) {
369 var baseTree
= GithubAPI
.getTree(this.user
, this.origin
.sha
);
372 .text("Unable to locate base tree.<br/>" + baseTree
.status
+ ": " + baseTree
.statusText
)
374 title
: "Github commit error",
380 console
.log("Base tree: " + baseTree
.url
);
381 var newBlob
= GithubAPI
.createBlob(this.user
, edit
.newContent
);
384 .text("Unable to create new blob.<br/>" + newBlob
.status
+ ": " + newBlob
.statusText
)
386 title
: "Github commit error",
392 console
.log("New blob: " + newBlob
.url
);
393 var newTree
= GithubAPI
.createTree(this.user
, baseTree
, edit
.location
.path
, newBlob
);
396 .text("Unable to create new tree.<br/>" + newTree
.status
+ ": " + newTree
.statusText
)
398 title
: "Github commit error",
404 console
.log("New tree: " + newTree
.url
);
405 var newCommit
= GithubAPI
.createCommit(this.user
, edit
.message
, baseTree
.sha
, newTree
);
408 .text("Unable to create new commit.<br/>" + newCommit
.status
+ ": " + newCommit
.statusText
)
410 title
: "Github commit error",
416 console
.log("New commit: " + newCommit
.url
);
417 var pullRequest
= GithubAPI
.createPullRequest(this.user
, edit
.title
, "Pull request from Nitdoc", this.origin
, newCommit
.sha
);
418 if(!pullRequest
.number
) {
420 .text("Unable to create pull request.<br/>" + pullRequest
.status
+ ": " + pullRequest
.statusText
)
422 title
: "Github commit error",
428 console
.log("New pull request: " + pullRequest
.url
);
432 // close previously opened pull request
433 _closePullRequest
: function(number
) {
434 var requests
= JSON
.parse(localStorage
.requests
);
435 if(!requests
[number]) {
437 .text("Unable to close pull request.<br/>" + "Pull request " + number
+ "not found")
439 title
: "Github commit error",
445 // close pull request
446 var res
= GithubAPI
.updatePullRequest(this.user
, "Closed from Nitdoc", "", "closed", requests
[number].request
);
449 .text("Unable to close pull request.<br/>" + res
.status
+ ": " + res
.statusText
)
451 title
: "Github commit error",
457 // update in localstorage
458 requests
[number].isClosed
= true;
459 localStorage
.requests
= JSON
.stringify(requests
);
464 _parseUpstream
: function(upstream
) {
465 var parts
= upstream
.split(":");
474 _getFileContent
: function(githubUrl
) {
475 var origFile
= GithubAPI
.getFile(this.user
, githubUrl
);
476 if(!origFile
.content
) {
478 .text("Unable to locate source file.<br/>" + origFile
.status
+ ": " + origFile
.statusText
)
480 title
: "Github commit error",
486 var base64Content
= origFile
.content
.substring(0, origFile
.content
.length
- 1)
487 return base64Content
.base64Decode();
490 _mergeComment
: function(fileContent
, comment
, location
) {
491 // replace comment in file content
492 var res
= new String();
493 var lines
= fileContent
.split("\n");
494 // copy lines fron 0 to lstart
495 for(var i
= 0; i
< location
.lstart
- 1; i
++) {
496 res
+= lines
[i] + "\n";
499 if(comment
&& comment
!= "") {
500 var commentLines
= comment
.split("\n");
501 for(var i
= 0; i
< commentLines
.length
; i
++) {
502 var line
= commentLines
[i];
503 var tab
= location
.tabpos
> 1 ?
"\t" : "";
504 res
+= tab
+ (line
.length
> 0 ?
"# " : "#") + line
+ "\n";
507 // copy lines fron lend to end
508 for(var i
= location
.lend
- 1; i
< lines
.length
; i
++) {
510 if(i
< lines
.length
- 1) { res
+= "\n"; }
517 _openCommentBox
: function(event
, baseArea
) {
518 baseArea
.commentbox("open", this.user
);
521 _doCancelRequest
: function(event
, baseArea
, request
) {
522 this._closePullRequest(request
.request
.number
);
523 this._reloadComments();
526 _doUpdateRequest
: function(event
, baseArea
, request
) {
527 baseArea
.commentbox("open", this.user
, request
.request
.number
);
531 // Get github plugin data
532 var upstream
= $
("body").attr("data-github-upstream");
533 var basesha1
= $
("body").attr("data-github-base-sha1");
534 if(upstream
&& basesha1
) {
535 GithubUI
.init(upstream
, basesha1
);