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