Merge branch 'privat' into topub
[nit.git] / share / nitdoc / scripts / Nitdoc.GitHub.js
1 /* This file is part of NIT ( http://www.nitlanguage.org ).
2
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
6
7 http://www.apache.org/licenses/LICENSE-2.0
8
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.
14
15 Documentation generator for the nit language.
16 Generate API documentation in HTML format from nit source code.
17 */
18
19 /*
20 * Nitdoc.Github comment edition module
21 *
22 * Allows user to modify source code comments directly from the Nitdoc
23 */
24
25 var Nitdoc = Nitdoc || {};
26
27 Nitdoc.GitHub = {}; // Declare Nitdoc.GitHub submodule
28
29 // Load GitHub UI
30 $(document).ready(function() {
31 //FIXME base should be choosen by user
32 var upstream = $("body").attr("data-github-upstream");
33 var basesha1 = $("body").attr("data-github-base-sha1");
34 if(upstream && basesha1) {
35 Nitdoc.GitHub.UI.init(upstream, basesha1);
36 }
37 });
38
39 /*
40 * Nitdoc.Github.UI for comment edition module
41 */
42 Nitdoc.GitHub.UI = function() {
43 var openedComments = 0; // currently edited comments count
44 var user = false; // logged user
45 var origin;
46
47 var init = function(upstream, basesha1) {
48 console.log("init GitHub module (upstream: "+ upstream +", base: " + basesha1 + ")");
49
50 // parse origin
51 var parts = upstream.split(":");
52 if(parts.length < 3) {
53 console.error("Incorrect upstream name `" + upstream + "`, should be of the form user:repo:branch");
54 }
55 origin = {
56 user: parts[0],
57 repo: parts[1],
58 branch: parts[2],
59 sha: basesha1
60 };
61
62 // check local session
63 if(localStorage.user) {
64 var session = JSON.parse(localStorage.user);
65 var user = tryLogin(session.login, Base64.decode(session.password), session.repo, session.branch);
66 if(!user.login) {
67 console.log("Session found but authentification failed");
68 localStorage.clear();
69 }
70 } else {
71 console.log("No session found");
72 }
73
74 // activate ui
75 Nitdoc.GitHub.LoginBox.init("nav.main ul");
76 if(user && user.login) {
77 Nitdoc.GitHub.LoginBox.displayLogout(origin, user);
78 activate(user);
79 } else {
80 Nitdoc.GitHub.LoginBox.displayLogin();
81 }
82
83 // Prevent page unload if there is comments in editing mode
84 $(window).on('beforeunload', function() {
85 if(Nitdoc.GitHub.UI.getOpenedComments() > 0){
86 return "There is uncommited modified comments. Are you sure you want to leave this page?";
87 }
88 });
89 }
90
91 // Activate comment UI for a logged user
92 var activate = function(loggedUser) {
93 // Save session
94 user = loggedUser;
95 saveSession(user);
96
97 // check local storage synchro with branch
98 if(localStorage.base != origin.sha) {
99 console.log("Base changed: cleaned cache");
100 localStorage.requests = "[]";
101 localStorage.base = origin.sha;
102 }
103
104 attachCommentEvents();
105 reloadComments();
106 }
107
108 // clear storage
109 var disactivate = function() {
110 if(Nitdoc.GitHub.UI.getOpenedComments() > 0){
111 if(!confirm('There is uncommited modified comments. Are you sure you want to leave this page?')) {
112 return false;
113 }
114 }
115 // close session and purge cookie
116 localStorage.clear();
117 $(window).unbind('beforeunload');
118 window.location.reload();
119 }
120
121 // Attempt login through GitHub API
122 var tryLogin = function(login, password, repo, branch) {
123 var user = new Nitdoc.GitHub.User(login, password, repo, branch);
124 if(!Nitdoc.GitHub.API.login(user)) {
125 return "error:login";
126 }
127 // check github profile fields
128 if(!user.infos.name || !user.infos.email) {
129 return "error:profile";
130 }
131 // check correct base commit
132 var commit = Nitdoc.GitHub.API.getCommit(user, origin.sha);
133 if(!commit || !commit.sha) {
134 return "error:sha";
135 }
136 return user;
137 }
138
139 // Attach edit button on each comment
140 var attachCommentEvents = function() {
141 // Blocks without comment
142 $("span.noComment").each(function() {
143 //FIXME this should be done by nitdoc
144 var baseComment = $(this).parent().prev();
145 var location = Nitdoc.GitHub.Utils.parseLocation(baseComment.attr("data-comment-location"));
146 var locString = location.path + ":" + location.lstart + "," + location.tabpos + "--" + location.lstart + ",0";
147 baseComment.attr("data-comment-location", locString);
148 $(this).html("<a class='nitdoc-github-editComment noComment'>add comment</a> for ");
149 $(this).addClass("nitdoc-github-editComment");
150 });
151 // Blocks with comment
152 $('.description div.comment').each(function() {
153 var p = $(this).next();
154 p.prepend("<span class='nitdoc-github-editComment'><a class='nitdoc-github-editComment'>edit comment</a> for </span>")
155 });
156
157 // Attach links events
158 $('a.nitdoc-github-editComment').each(function() {
159 $(this).css("cursor", "pointer")
160 $(this).click(function() {
161 // hide link
162 $(this).parent().hide();
163 // add infos
164 var infos = {};
165 var baseTextarea;
166 if(!$(this).hasClass("noComment")) {
167 $(this).parent().parent().prev().hide();
168 baseTextarea = $(this).parent().parent().prev().prev();
169 } else {
170 baseTextarea = $(this).parent().parent().prev();
171 infos.isNew = true;
172 }
173 infos.user = Nitdoc.GitHub.UI.getUser();
174 infos.location = Nitdoc.GitHub.Utils.parseLocation(baseTextarea.attr("data-comment-location"));
175 infos.namespace = baseTextarea.attr("data-comment-namespace");
176 infos.oldComment = baseTextarea.val();
177 var box = new Nitdoc.GitHub.CommentBox(infos);
178 box.open(baseTextarea);
179 });
180 });
181 }
182
183 // reload comments from saved pull request
184 var reloadComments = function() {
185 if(!localStorage.requests){ return; }
186 var requests = JSON.parse(localStorage.requests);
187 var converter = new Markdown.Converter();
188 // Look for modified comments in page
189 for(i in requests) {
190 if(!requests[i]) { continue; }
191 var request = requests[i];
192 $("textarea[data-comment-location=\"" + request.location + "\"]").each(function () {
193 var div = $(this).next();
194 if(request.isClosed) {
195 if(div.is("div.comment.newComment")) {
196 // hide empty comment
197 div.next().remove();
198 div.next().find("span.noComment").show();
199 div.remove();
200 } else if(div.is("div.comment.locked")) {
201 // unlock comment
202 div.empty();
203 div.append(converter.makeHtml($(this).text()));
204 div.removeClass("locked");
205 div.css("cursor", "pointer")
206 div.next().remove();
207 div.next().find("span.nitdoc-github-editComment").show();
208 }
209 } else {
210 // create div for the new coment
211 if(!div.is("div.comment")) {
212 $(this).after("<div class='comment newComment'></div>");
213 div = $(this).next();
214 }
215 // lock modified comment
216 if(!div.hasClass("locked")) {
217 // convert modified comment to markdown
218 div.empty()
219 div.append(converter.makeHtml(Base64.decode(request.comment)));
220 // lock click
221 div.css("cursor", "auto");
222 div.addClass("locked");
223 div.next().find("span.nitdoc-github-editComment").hide();
224 div.after(
225 $("<p class='locked inheritance'>")
226 .text("comment modified in ")
227 .append("<a href='"+ request.request.html_url +"' target='_blank' title='Review on GitHub'>pull request #"+ request.request.number +"</a>")
228 .append(" ")
229 .append(
230 $("<a data-pullrequest-number='"+ request.request.number +"' class='nitdoc-github-update'>update</a>")
231 .click(function (){
232 $(this).parent().hide();
233 div.hide();
234 var baseTextarea = div.prev();
235 // add infos
236 var infos = {};
237 infos.user = Nitdoc.GitHub.UI.getUser();
238 infos.location = Nitdoc.GitHub.Utils.parseLocation(baseTextarea.attr("data-comment-location"));
239 infos.namespace = baseTextarea.attr("data-comment-namespace");
240 infos.oldComment = baseTextarea.val();
241 infos.requestID = $(this).attr("data-pullrequest-number");
242 var box = new Nitdoc.GitHub.CommentBox(infos);
243 box.open(baseTextarea);
244 })
245 )
246 .append(" ")
247 .append(
248 $("<a data-pullrequest-number='"+ request.request.number +"' class='nitdoc-github-cancel'>cancel</a>")
249 .click(function (){
250 ui.closePullRequest($(this).attr("data-pullrequest-number"));
251 ui.reloadComments();
252 })
253 )
254 );
255 }
256 // hide "add comment" link
257 if(div.hasClass("newComment")) {
258 div.next().next().find("span.noComment").hide();
259 }
260 }
261
262 });
263 }
264 }
265
266 // Commit changes and send pull request
267 var saveChanges = function(infos) {
268 // if pull request update close existing pull request for the comment
269 if(infos.requestID) {
270 closePullRequest(infos.requestID);
271 }
272
273 // forge commit
274 var fileContent = getFileContent(infos.location.path);
275 infos.newContent = Nitdoc.GitHub.Utils.mergeComment(fileContent, infos.newComment, infos.location);
276
277 // commit
278 infos.request = pushChanges(infos)
279 if(!infos.request) {
280 Nitdoc.GitHub.ModalBox.open("Unable to commit changes!", response, true);
281 return;
282 }
283 saveRequest(infos);
284
285 // close boxes and reload comments
286 Nitdoc.GitHub.CommitBox.close();
287 infos.commentBox.close();
288 reloadComments();
289 }
290
291 /*
292 Creating a new pull request with the new comment take 5 steps:
293 1. get the base tree from latest commit
294
295 2. create a new blob with updated file content
296 3. post a new tree from base tree and blob
297 4. post the new commit with new tree
298 5. create the pull request
299 */
300 var pushChanges = function(infos) {
301 var baseTree = Nitdoc.GitHub.API.getTree(user, origin.sha);
302 if(!baseTree.sha) {
303 Nitdoc.GitHub.ModalBox.open("Unable to locate base tree!", baseTree.status + ": " + baseTree.statusText, true);
304 return false;
305 }
306 console.log("Base tree: " + baseTree.url);
307 var newBlob = Nitdoc.GitHub.API.createBlob(user, infos.newContent);
308 if(!newBlob.sha) {
309 Nitdoc.GitHub.ModalBox.open("Unable to create new blob!", newBlob.status + ": " + newBlob.statusText, true);
310 return false;
311 }
312 console.log("New blob: " + newBlob.url);
313 var newTree = Nitdoc.GitHub.API.createTree(user, baseTree, infos.location.path, newBlob);
314 if(!newTree.sha) {
315 Nitdoc.GitHub.ModalBox.open("Unable to create new tree!", newTree.status + ": " + newTree.statusText, true);
316 return false;
317 }
318 console.log("New tree: " + newTree.url);
319 var newCommit = Nitdoc.GitHub.API.createCommit(user, infos.message, baseTree.sha, newTree);
320 if(!newCommit.sha) {
321 Nitdoc.GitHub.ModalBox.open("Unable to create new commit!", newCommit.status + ": " + newCommit.statusText, true);
322 return false;
323 }
324 console.log("New commit: " + newCommit.url);
325 var pullRequest = Nitdoc.GitHub.API.createPullRequest(user, infos.message.split("\n\n")[0], "Pull request from Nitdoc", origin, newCommit.sha);
326 if(!pullRequest.number) {
327 Nitdoc.GitHub.ModalBox.open("Unable to create pull request!", pullRequest.status + ": " + pullRequest.statusText, true);
328 return false;
329 }
330 console.log("New pull request: " + pullRequest.url);
331 return pullRequest;
332 }
333
334 // close previously opened pull request
335 var closePullRequest = function(number) {
336 var requests = JSON.parse(localStorage.requests);
337 if(!requests[number]) {
338 Nitdoc.GitHub.ModalBox.open("Unable to close pull request!", "Pull request " + number + "not found", true);
339 return false;
340 }
341 // close pull request
342 var res = Nitdoc.GitHub.API.updatePullRequest(user, "Canceled from Nitdoc", "", "closed", requests[number].request);
343 if(!res.id) {
344 Nitdoc.GitHub.ModalBox.open("Unable to close pull request!", res.status + ": " + res.statusText, true);
345 return false;
346 }
347 // update in localstorage
348 requests[number].isClosed = true;
349 localStorage.requests = JSON.stringify(requests);
350 }
351
352 // Get file content from github
353 var getFileContent = function(githubUrl) {
354 var origFile = Nitdoc.GitHub.API.getFile(user, githubUrl);
355 if(!origFile.content) {
356 Nitdoc.GitHub.ModalBox.open("Unable to locate source file!", origFile.status + ": " + origFile.statusText, true);
357 return;
358 }
359 var base64Content = origFile.content.substring(0, origFile.content.length - 1)
360 return Base64.decode(base64Content);
361 }
362
363 // save pull request in local storage
364 var saveRequest = function(infos) {
365 var requests = [];
366 if(localStorage.requests) {requests = JSON.parse(localStorage.requests)}
367 requests[infos.request.number] = {
368 request: infos.request,
369 location: infos.location.origin,
370 comment: Base64.encode(infos.newComment)
371 };
372 localStorage.requests = JSON.stringify(requests);
373 }
374
375 // save user in local storage
376 var saveSession = function(user) {
377 var session = {
378 login: user.login,
379 password: Base64.encode(user.password),
380 repo: user.repo,
381 branch: user.branch,
382 };
383 localStorage.user = JSON.stringify(session);
384 }
385
386 // accessors
387
388 var getUser = function() { return user; }
389 var getOrigin = function() { return origin; }
390 var getOpenedComments = function() { return openedComments; }
391 var addOpenedComments = function() { openedComments += 1; }
392 var remOpenedComments = function() { openedComments -= 1; }
393
394 // public interface
395 var ui = {
396 init: init,
397 tryLogin: tryLogin,
398 activate: activate,
399 disactivate: disactivate,
400 getUser: getUser,
401 getOrigin: getOrigin,
402 getOpenedComments: getOpenedComments,
403 addOpenedComments: addOpenedComments,
404 remOpenedComments: remOpenedComments,
405 saveChanges: saveChanges,
406 closePullRequest: closePullRequest,
407 reloadComments: reloadComments
408 }
409
410 return ui;
411 }();
412
413 /*
414 * GitHub API user object
415 */
416 Nitdoc.GitHub.User = function(login, password, repo, branch) {
417 this.login = login;
418 this.password = password;
419 this.repo = repo;
420 this.auth = "Basic " + Base64.encode(login + ':' + password);
421 this.branch = branch;
422 };
423
424 /*
425 * GitHub API module
426 */
427 Nitdoc.GitHub.API = function() {
428
429 // try to login the user to github API
430 var login = function(user) {
431 var res = false;
432 $.ajax({
433 beforeSend: function (xhr) {
434 xhr.setRequestHeader ("Authorization", user.auth);
435 },
436 type: "GET",
437 url: "https://api.github.com/repos/" + user.login + "/" + user.repo,
438 async: false,
439 dataType: 'json',
440 success: function() {
441 res = true;
442 }
443 });
444 user.infos = getUserInfos(user);
445 user.signedOff = getSignedOff(user)
446 return res;
447 }
448
449 // request for user github account infos
450 var getUserInfos = function(user) {
451 var res = false;
452 $.ajax({
453 beforeSend: function (xhr) {
454 xhr.setRequestHeader ("Authorization", user.auth);
455 },
456 type: "GET",
457 url: "https://api.github.com/users/" + user.login,
458 async: false,
459 dataType: 'json',
460 success: function(response) {
461 res = response;
462 },
463 error: function(response) {
464 res = response;
465 }
466 });
467 return res;
468 }
469
470 // build signedoff user default signature
471 var getSignedOff = function(user) {
472 return user.infos.name + " <" + user.infos.email + ">";
473 }
474
475 // get the branches list from a repo
476 var getBranches = function(user) {
477 var res = false;
478 $.ajax({
479 beforeSend: function (xhr) {
480 xhr.setRequestHeader ("Authorization", user.auth);
481 },
482 type: "GET",
483 url: "https://api.github.com/repos/" + user.login + "/" + user.repo + "/branches",
484 async: false,
485 dataType: 'json',
486 success: function(response) {
487 res = response;
488 },
489 error: function(response) {
490 res = response;
491 }
492 });
493 return res;
494 }
495
496 /* GitHub commits */
497
498 // get the latest commit on `branchName`
499 var getCommit = function(user, sha) {
500 var res = false;
501 $.ajax({
502 beforeSend: function (xhr) {
503 xhr.setRequestHeader ("Authorization", user.auth);
504 },
505 type: "GET",
506 url: "https://api.github.com/repos/" + user.login + "/" + user.repo + "/git/commits/" + sha,
507 async: false,
508 dataType: 'json',
509 success: function(response) {
510 res = response;
511 },
512 error: function(response) {
513 res = response;
514 }
515 });
516 return res;
517 }
518
519 // get the base tree for a commit sha
520 var getTree = function(user, sha) {
521 var res = false;
522 $.ajax({
523 beforeSend: function (xhr) {
524 xhr.setRequestHeader ("Authorization", user.auth);
525 },
526 type: "GET",
527 url: "https://api.github.com/repos/" + user.login + "/" + user.repo + "/git/trees/" + sha + "?recursive=1",
528 async: false,
529 dataType: 'json',
530 success: function(response) {
531 res = response;
532 },
533 error: function(response) {
534 res = response;
535 }
536 });
537 return res;
538 }
539
540 // create a new blob
541 var createBlob = function(user, content) {
542 var res = false;
543 $.ajax({
544 beforeSend: function (xhr) {
545 xhr.setRequestHeader ("Authorization", user.auth);
546 },
547 type: "POST",
548 url: "https://api.github.com/repos/" + user.login + "/" + user.repo + "/git/blobs",
549 async: false,
550 dataType: 'json',
551 data: JSON.stringify({
552 content: Base64.encode(content),
553 encoding: "base64"
554 }),
555 success: function(response) {
556 res = response;
557 },
558 error: function(response) {
559 res = response;
560 }
561 });
562 return res;
563 }
564
565 // create a new tree from a base tree
566 var createTree = function(user, baseTree, path, blob) {
567 var res = false;
568 $.ajax({
569 beforeSend: function (xhr) {
570 xhr.setRequestHeader ("Authorization", user.auth);
571 },
572 type: "POST",
573 url: "https://api.github.com/repos/" + user.login + "/" + user.repo + "/git/trees",
574 data: JSON.stringify({
575 base_tree: baseTree.sha,
576 tree: [{
577 path: path,
578 mode: "100644", // file (blob)
579 type: "blob",
580 sha: blob.sha
581 }]
582 }),
583 async: false,
584 dataType: 'json',
585 success: function(response) {
586 res = response;
587 },
588 error: function(response) {
589 res = response;
590 }
591 });
592 return res;
593 }
594
595 // create a new commit
596 var createCommit = function(user, message, parentCommit, tree) {
597 var res = false;
598 $.ajax({
599 beforeSend: function (xhr) {
600 xhr.setRequestHeader ("Authorization", user.auth);
601 },
602 type: "POST",
603 url: "https://api.github.com/repos/" + user.login + "/" + user.repo + "/git/commits",
604 data: JSON.stringify({
605 message: message,
606 parents: parentCommit,
607 tree: tree.sha,
608 }),
609 async: false,
610 dataType: 'json',
611 success: function(response) {
612 res = response;
613 },
614 error: function(response) {
615 res = response;
616 }
617 });
618 return res;
619 }
620
621 // create a pull request
622 var createPullRequest = function(user, title, body, origin, head) {
623 var res = false;
624 $.ajax({
625 beforeSend: function (xhr) {
626 xhr.setRequestHeader ("Authorization", user.auth);
627 },
628 type: "POST",
629 url: "https://api.github.com/repos/" + origin.user + "/" + origin.repo + "/pulls",
630 data: JSON.stringify({
631 title: title,
632 body: body,
633 base: origin.branch,
634 head: user.login + ":" + head
635 }),
636 async: false,
637 dataType: 'json',
638 success: function(response) {
639 res = response;
640 },
641 error: function(response) {
642 res = response;
643 }
644 });
645 return res;
646 }
647
648 // update a pull request
649 var updatePullRequest = function(user, title, body, state, request) {
650 var res = false;
651 $.ajax({
652 beforeSend: function (xhr) {
653 xhr.setRequestHeader ("Authorization", user.auth);
654 },
655 type: "PATCH",
656 url: request.url,
657 data: JSON.stringify({
658 title: title,
659 body: body,
660 state: state
661 }),
662 async: false,
663 dataType: 'json',
664 success: function(response) {
665 res = response;
666 },
667 error: function(response) {
668 res = response;
669 }
670 });
671 return res;
672 }
673
674 /* Files */
675
676 var getFile = function(user, path, branch) {
677 var res = false;
678 $.ajax({
679 type: "GET",
680 url: "https://api.github.com/repos/" + user.login + "/" + user.repo + "/contents/" + path,
681 data: {
682 ref: branch
683 },
684 async: false,
685 dataType: 'json',
686 success: function(response) {
687 res = response;
688 },
689 error: function(response) {
690 res = response;
691 }
692 });
693 return res;
694 }
695
696 var api = {
697 login: login,
698 getCommit: getCommit,
699 getBranches: getBranches,
700 getTree: getTree,
701 createBlob: createBlob,
702 createTree: createTree,
703 createCommit: createCommit,
704 createPullRequest: createPullRequest,
705 updatePullRequest: updatePullRequest,
706 getFile: getFile
707 }
708
709 return api;
710 }();
711
712 /*
713 * Nitdoc GitHub loginbox module
714 */
715
716 Nitdoc.GitHub.LoginBox = function() {
717 var loginBox;
718 var loginBoxLi;
719 var loginBoxContent;
720
721 var init = function(containerSelector) {
722 loginBoxLi = $(document.createElement("li"))
723 .attr("id", "nitdoc-github-li")
724 .append(
725 $(document.createElement("a"))
726 .append(
727 $(document.createElement("img"))
728 .attr({
729 src: "resources/icons/github-icon.png",
730 alt: "GitHub"
731 })
732 .addClass("nitdoc-github-li-img")
733 )
734 .click(function() { Nitdoc.GitHub.LoginBox.toggle() })
735 )
736
737 loginBoxContent = $(document.createElement("div"));
738 loginBox = $(document.createElement("div"))
739 .attr("id", "nitdoc-github-loginbox")
740 .css("display", "none")
741 .append(
742 $(document.createElement("div"))
743 .addClass("nitdoc-github-loginbox-arrow")
744 .append("&nbsp;")
745 )
746 .append(loginBoxContent);
747
748 loginBoxLi.append(loginBox);
749 $(containerSelector).append(loginBoxLi);
750 }
751
752 // Panel of login box to display when the user is logged in
753 var displayLogout = function(origin, user) {
754 var panel = $(document.createElement("div"))
755 .append(
756 $(document.createElement("h3"))
757 .append("Signed in Github")
758 )
759 .append(
760 $(document.createElement("h4"))
761 .append("Hello ")
762 .append(
763 $(document.createElement("a"))
764 .attr("href", "https://github.com/" + user.login)
765 .append(user.login)
766 ).append("!")
767 )
768 .append(
769 $(document.createElement("label"))
770 .attr("for", "github-origin")
771 .append("Upstram branch")
772 )
773 .append(
774 $(document.createElement("a"))
775 .addClass("nitdoc-github-loginbox-githublink")
776 .attr({
777 title: "Open branch in GitHub",
778 href: "https://github.com/" + origin.user + "/" + origin.repo + "/tree/" + origin.branch,
779 target: "_blank"
780 })
781 .append(origin.user + ":" + origin.repo + ":" + origin.branch)
782 )
783 .append(
784 $(document.createElement("label"))
785 .attr("for", "github-base")
786 .append("Your branch")
787 )
788 .append(
789 $(document.createElement("a"))
790 .addClass("nitdoc-github-loginbox-githublink")
791 .attr({
792 title: "Open branch in GitHub",
793 href: "https://github.com/" + user.login + "/" + user.repo + "/tree/" + user.branch,
794 target: "_blank"
795 })
796 .append(origin.user + ":" + origin.repo + ":" + origin.branch)
797 )
798 .append(
799 $(document.createElement("button"))
800 .addClass("nitdoc-github-button")
801 .addClass("nitdoc-github-cancel")
802 .append(
803 $(document.createElement("img"))
804 .attr("src", "resources/icons/github-icon.png")
805 ).append("Sign Off")
806 .click(function() { // log out user
807 Nitdoc.GitHub.UI.disactivate();
808 Nitdoc.GitHub.LoginBox.toggle();
809 })
810 );
811 $(".nitdoc-github-li-img").attr("src", "resources/icons/github-icon-green.png");
812 loginBoxContent.empty()
813 loginBoxContent.append(panel);
814 }
815
816 // Panel of login box to display when the user is logged out
817 var displayLogin = function() {
818 var panel = $(document.createElement("form"))
819 .append(
820 $(document.createElement("h3"))
821 .append("Sign in Github")
822 )
823 .append(
824 $(document.createElement("label"))
825 .attr("for", "nitdoc-github-login-field")
826 .append("Username")
827 )
828 .append(
829 $(document.createElement("input"))
830 .attr({
831 id: "nitdoc-github-login-field",
832 type: "text"
833 })
834 )
835 .append(
836 $(document.createElement("label"))
837 .attr("for", "nitdoc-github-password-field")
838 .append("Password")
839 )
840 .append(
841 $(document.createElement("input"))
842 .attr({
843 id: "nitdoc-github-password-field",
844 type: "password"
845 })
846 )
847 .append(
848 $(document.createElement("label"))
849 .attr("for", "nitdoc-github-repo-field")
850 .append("Repository")
851 )
852 .append(
853 $(document.createElement("input"))
854 .attr({
855 id: "nitdoc-github-repo-field",
856 type: "text"
857 })
858 )
859 .append(
860 $(document.createElement("label"))
861 .attr("for", "nitdoc-github-branch-field")
862 .append("Branch")
863 )
864 .append(
865 $(document.createElement("input"))
866 .attr({
867 id: "nitdoc-github-branch-field",
868 type: "text"
869 })
870 )
871 .append(
872 $(document.createElement("button"))
873 .addClass("nitdoc-github-button")
874 .append(
875 $(document.createElement("img"))
876 .attr("src", "resources/icons/github-icon.png")
877 ).append("Sign In")
878 .click(function() {
879 var login = $('#nitdoc-github-login-field').val();
880 var password = $('#nitdoc-github-password-field').val();
881 var repo = $('#nitdoc-github-repo-field').val();
882 var branch = $('#nitdoc-github-branch-field').val();
883 if(!login || !password || !repo || !branch) {
884 Nitdoc.GitHub.ModalBox.open("Sign in error", "Please enter your GitHub username, password, repository and branch.", true);
885 } else {
886 var user = Nitdoc.GitHub.UI.tryLogin(login, password, repo, branch);
887 if(user == "error:login") {
888 Nitdoc.GitHub.ModalBox.open("Sign in error", "The username, password, repo or branch you entered is incorrect.", true);
889 } else if(user == "error:sha") {
890 Nitdoc.GitHub.ModalBox.open("Base commit not found", "The provided GitHub repository must contains the base commit '" + Nitdoc.GitHub.UI.getOrigin().sha + "'", true);
891 } else if(user == "error:profile") {
892 Nitdoc.GitHub.ModalBox.open("Incomplete GitHub profile", "Please set your public name and email in your <a href='https://github.com/settings/profile'>GitHub profile</a>.<br/><br/>Your public profile informations are used to sign-off your commits.", true);
893 } else {
894 Nitdoc.GitHub.UI.activate(user);
895 var origin = Nitdoc.GitHub.UI.getOrigin();
896 Nitdoc.GitHub.LoginBox.displayLogout(origin, user);
897 }
898 }
899 return false;
900 })
901 )
902 $(".nitdoc-github-li-img").attr("src", "resources/icons/github-icon.png");
903 loginBoxContent.empty()
904 loginBoxContent.append(panel);
905 }
906
907 var toggle = function() {
908 if(loginBox.is(':hidden')) {
909 loginBox.show();
910 if (!$('#loginGit').is(':hidden')) { $('#loginGit').focus(); }
911 } else {
912 loginBox.hide();
913 }
914 }
915
916 // Public interface
917 var loginbox = {
918 init: init,
919 displayLogin: displayLogin,
920 displayLogout: displayLogout,
921 toggle: toggle,
922
923 };
924
925 return loginbox;
926 }();
927
928 /*
929 * Nitdoc.GitHub.CommentBox class
930 */
931
932 // Init new modal box instance
933 Nitdoc.GitHub.CommentBox = function(infos) {
934 this.infos = infos;
935 this.commentBoxDiv;
936 }
937
938 Nitdoc.GitHub.CommentBox.prototype.open = function(baseArea) {
939 Nitdoc.GitHub.UI.addOpenedComments();
940 var instance = this;
941
942 if(this.infos.requestID) {
943 // get comment from last pull request
944 var requests = JSON.parse(localStorage.requests);
945 this.infos.newComment = Base64.decode(requests[this.infos.requestID].comment);
946 } else {
947 this.infos.newComment = false;
948 }
949
950 // create comment box
951 var tarea = $(document.createElement("textarea"))
952 .append(this.infos.newComment === false? this.infos.oldComment: this.infos.newComment)
953 .keyup(function(event) {
954 $(event.target).css("height", (event.target.value.split(/\r|\n/).length * 16) + "px");
955 if ( (!instance.infos.requestID && $(event.target).val() != instance.infos.oldComment) || (instance.infos.requestID && $(event.target).val() != instance.infos.oldComment && $(event.target).val() != instance.infos.newComment) ) {
956 $(event.target).parent().find("button.nitdoc-github-commit").removeAttr("disabled");
957 } else {
958 $(event.target).parent().find("button.nitdoc-github-commit").attr("disabled", "disabled");
959 }
960 })
961 .keydown(function(event) {
962 if(event.keyCode == 13){
963 $(event.target).css("height", ($(event.target).outerHeight() + 6) + "px");
964 }
965 });
966
967 this.commentBoxDiv = $(document.createElement("div"))
968 .addClass("nitdoc-github-commentbox")
969 .append(tarea)
970 .append(
971 $(document.createElement("a"))
972 .addClass("nitdoc-github-preview")
973 .click(function() {
974 var converter = new Markdown.Converter()
975 var html = converter.makeHtml(tarea.val());
976 Nitdoc.GitHub.ModalBox.open("Preview", html, false);
977 })
978 )
979 .append(
980 $(document.createElement("button"))
981 .addClass("nitdoc-github-button")
982 .addClass("nitdoc-github-commit")
983 .append("Commit...")
984 .click(function() {
985 instance.infos.newComment = tarea.val();
986 instance.infos.commentBox = instance;
987 Nitdoc.GitHub.CommitBox.open(instance.infos);
988 })
989 )
990 .append(
991 $(document.createElement("button"))
992 .addClass("nitdoc-github-button")
993 .addClass("nitdoc-github-cancel")
994 .append("Cancel")
995 .click(function() {instance.close()})
996 );
997
998 baseArea.after(this.commentBoxDiv);
999 var cbWidth = this.commentBoxDiv.innerWidth();
1000 var taWidth = tarea.outerWidth();
1001 tarea.width(cbWidth - (taWidth - cbWidth));
1002 tarea.trigger("keyup");
1003 tarea.focus();
1004 }
1005
1006 Nitdoc.GitHub.CommentBox.prototype.close = function() {
1007 Nitdoc.GitHub.UI.remOpenedComments();
1008 if(this.infos.isNew) {
1009 this.commentBoxDiv.next().find("span.nitdoc-github-editComment").show();
1010 } else if(this.infos.requestID) {
1011 this.commentBoxDiv.next().show();
1012 this.commentBoxDiv.next().next().show();
1013 } else {
1014 this.commentBoxDiv.next().show();
1015 this.commentBoxDiv.next().next().find("span.nitdoc-github-editComment").show();
1016 }
1017 this.commentBoxDiv.remove();
1018 }
1019
1020 /*
1021 * Nitdoc.GitHub.ModalBox class
1022 */
1023
1024 // Init new modal box instance
1025 Nitdoc.GitHub.ModalBox = function() {
1026
1027 // Open modal box instance
1028 var open = function(title, content, isError) {
1029 $("body").append(
1030 $(document.createElement("div"))
1031 .attr("id", "nitdoc-github-modal-fade")
1032 .addClass("nitdoc-github-fade")
1033 )
1034 .append(
1035 $(document.createElement("div"))
1036 .attr("id", "nitdoc-github-modal")
1037 .addClass("nitdoc-github-modal")
1038 .append(
1039 $(document.createElement("a"))
1040 .addClass("nitdoc-github-close")
1041 .attr("title", "Close")
1042 .append("x")
1043 .click(function() { Nitdoc.GitHub.ModalBox.close() })
1044 )
1045 .append("<h3>" + title + "</h3>")
1046 .append("<div>" + content + "</div>")
1047 .append(
1048 $(document.createElement("div"))
1049 .addClass("nitdoc-github-buttons")
1050 .append(
1051 $(document.createElement("button"))
1052 .addClass("nitdoc-github-button")
1053 .append("Ok")
1054 .click(function() { Nitdoc.GitHub.ModalBox.close() })
1055 )
1056 )
1057 );
1058
1059 if(isError) {
1060 $("#nitdoc-github-modal").addClass("nitdoc-github-error");
1061 }
1062
1063 $("#nitdoc-github-modal")
1064 .css({
1065 top: "50%",
1066 marginTop: -($("#nitdoc-github-modal").outerHeight() / 2) + "px",
1067 left: "50%",
1068 marginLeft: -($("#nitdoc-github-modal").outerWidth() / 2) + "px"
1069 })
1070 .find("button.nitdoc-github-button").focus();
1071 }
1072
1073 // Close modal box instance
1074 var close = function() {
1075 $("#nitdoc-github-modal").remove();
1076 $("#nitdoc-github-modal-fade").remove();
1077 }
1078
1079 // Public interface
1080 var modalBox = {
1081 open: open,
1082 close: close
1083 };
1084
1085 return modalBox;
1086 }();
1087
1088 /*
1089 * Nitdoc.GitHub.CommitBox instance
1090 */
1091
1092 // Init new commit box instance
1093 Nitdoc.GitHub.CommitBox = function() {
1094
1095 // Open commit box instance
1096 var open = function(infos) {
1097 $("body").append(
1098 $(document.createElement("div"))
1099 .attr("id", "nitdoc-github-commitBox-fade")
1100 .addClass("nitdoc-github-fade")
1101 );
1102 $("body").append(
1103 $(document.createElement("div"))
1104 .attr("id", "nitdoc-github-commitBox")
1105 .addClass("nitdoc-github-modal")
1106 .append(
1107 $(document.createElement("a"))
1108 .addClass("nitdoc-github-close")
1109 .attr("title", "Close")
1110 .append("x")
1111 .click(function() { Nitdoc.GitHub.CommitBox.close() })
1112 )
1113 .append("<h3>Commit changes</h3>")
1114 .append(
1115 $(document.createElement("div"))
1116 .append(
1117 $(document.createElement("label"))
1118 .attr("for", "nitdoc-github-commit-message")
1119 )
1120 .append("<br/>")
1121 .append(
1122 $(document.createElement("textarea"))
1123 .attr("id", "nitdoc-github-commit-message")
1124 .append("doc: " + (infos.isNew ? "added" : "modified") + " comment for " + infos.namespace)
1125 )
1126 .append("<br/>")
1127 .append(
1128 $(document.createElement("input"))
1129 .attr({
1130 id: "nitdoc-github-commit-signedoff",
1131 type: "checkbox",
1132 value: "Signed-off-by: " + infos.user.signedOff
1133 })
1134 .change(function(e) {
1135 if ($(this).is(':checked')) {
1136 $("#nitdoc-github-commit-button").removeAttr("disabled");
1137 } else {
1138 $("#nitdoc-github-commit-button").attr("disabled", "disabled");
1139 }
1140 })
1141 )
1142 .append(
1143 $(document.createElement("label"))
1144 .attr("for", "nitdoc-github-commit-signedoff")
1145 .text("Signed-off-by: " + infos.user.signedOff)
1146 )
1147 ).append(
1148 $(document.createElement("div"))
1149 .addClass("nitdoc-github-buttons")
1150 .append(
1151 $(document.createElement("button"))
1152 .attr({
1153 id: "nitdoc-github-commit-button",
1154 disabled: "disabled"
1155 })
1156 .addClass("nitdoc-github-button")
1157 .append(
1158 $(document.createElement("img"))
1159 .attr("src", "resources/icons/github-icon.png")
1160 )
1161 .append("Commit")
1162 .mousedown(function() {
1163 $(this).text("Commiting...");
1164 })
1165 .mouseup(function() {
1166 infos.message = $("#nitdoc-github-commit-message").val() + "\n\n" + infos.user.signedOff;
1167 Nitdoc.GitHub.UI.saveChanges(infos);
1168 })
1169 )
1170 )
1171 );
1172
1173 $("#nitdoc-github-commitBox")
1174 .css({
1175 top: "50%",
1176 marginTop: -($("#nitdoc-github-commitBox").outerHeight() / 2) + "px",
1177 left: "50%",
1178 marginLeft: -($("#nitdoc-github-commitBox").outerWidth() / 2) + "px"
1179 })
1180 .find("#nitdoc-github-commit-message").focus();
1181 }
1182
1183 // Close commit box instance
1184 var close = function() {
1185 $("#nitdoc-github-commitBox").remove();
1186 $("#nitdoc-github-commitBox-fade").remove();
1187 }
1188
1189 // Public interface
1190 var commitBox = {
1191 open: open,
1192 close: close
1193 }
1194 return commitBox;
1195 }();
1196
1197 /*
1198 * Nitdoc.GitHub.Utils module
1199 */
1200
1201 Nitdoc.GitHub.Utils = function() {
1202 // Extract infos from string location "lib/standard/collection/array.nit:457,1--458,0"
1203 var parseLocation = function(location) {
1204 var parts = location.split(":");
1205 var loc = new Object();
1206 loc.origin = location;
1207 loc.path = parts[0];
1208 loc.lstart = parseInt(parts[1].split("--")[0].split(",")[0]);
1209 loc.tabpos = parseInt(parts[1].split("--")[0].split(",")[1]);
1210 loc.lend = parseInt(parts[1].split("--")[1].split(",")[0]);
1211 return loc;
1212 }
1213
1214 // Meld modified comment into file conten
1215 var mergeComment = function(fileContent, comment, location) {
1216 // replace comment in file content
1217 var res = new String();
1218 var lines = fileContent.split("\n");
1219 // copy lines fron 0 to lstart
1220 for(var i = 0; i < location.lstart - 1; i++) {
1221 res += lines[i] + "\n";
1222 }
1223 // set comment
1224 if(comment && comment != "") {
1225 var commentLines = comment.split("\n");
1226 for(var i = 0; i < commentLines.length; i++) {
1227 var line = commentLines[i];
1228 var tab = location.tabpos > 1 ? "\t" : "";
1229 res += tab + (line.length > 0 ? "# " : "#") + line + "\n";
1230 }
1231 }
1232 // copy lines fron lend to end
1233 for(var i = location.lend - 1; i < lines.length; i++) {
1234 res += lines[i];
1235 if(i < lines.length - 1) { res += "\n"; }
1236 }
1237 return res;
1238 }
1239
1240 // Public interface
1241 var utils = {
1242 parseLocation: parseLocation,
1243 mergeComment: mergeComment
1244 };
1245
1246 return utils;
1247 }();
1248