ni_nitdoc: simplified github option
[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 .append(
793 $(document.createElement("img"))
794 .attr("src", "resources/icons/github-icon.png")
795 ).append("Sign Off")
796 .click(function() { // log out user
797 Nitdoc.GitHub.UI.disactivate();
798 Nitdoc.GitHub.LoginBox.toggle();
799 })
800 );
801 $(".nitdoc-github-li-img").attr("src", "resources/icons/github-icon-green.png");
802 loginBoxContent.empty()
803 loginBoxContent.append(panel);
804 }
805
806 // Panel of login box to display when the user is logged out
807 var displayLogin = function() {
808 var panel = $(document.createElement("form"))
809 .append(
810 $(document.createElement("label"))
811 .attr("for", "nitdoc-github-login-field")
812 .append("Username")
813 )
814 .append(
815 $(document.createElement("input"))
816 .attr({
817 id: "nitdoc-github-login-field",
818 type: "text"
819 })
820 )
821 .append(
822 $(document.createElement("label"))
823 .attr("for", "nitdoc-github-password-field")
824 .append("Password")
825 )
826 .append(
827 $(document.createElement("input"))
828 .attr({
829 id: "nitdoc-github-password-field",
830 type: "password"
831 })
832 )
833 .append(
834 $(document.createElement("label"))
835 .attr("for", "nitdoc-github-repo-field")
836 .append("Repository")
837 )
838 .append(
839 $(document.createElement("input"))
840 .attr({
841 id: "nitdoc-github-repo-field",
842 type: "text"
843 })
844 )
845 .append(
846 $(document.createElement("label"))
847 .attr("for", "nitdoc-github-branch-field")
848 .append("Branch")
849 )
850 .append(
851 $(document.createElement("input"))
852 .attr({
853 id: "nitdoc-github-branch-field",
854 type: "text"
855 })
856 )
857 .append(
858 $(document.createElement("button"))
859 .addClass("nitdoc-github-button")
860 .append(
861 $(document.createElement("img"))
862 .attr("src", "resources/icons/github-icon.png")
863 ).append("Sign In")
864 .click(function() {
865 var login = $('#nitdoc-github-login-field').val();
866 var password = $('#nitdoc-github-password-field').val();
867 var repo = $('#nitdoc-github-repo-field').val();
868 var branch = $('#nitdoc-github-branch-field').val();
869 if(!login || !password || !repo || !branch) {
870 Nitdoc.GitHub.ModalBox.open("Sign in error", "Please enter your GitHub username, password, repository and branch.", true);
871 } else {
872 var user = Nitdoc.GitHub.UI.tryLogin(login, password, repo, branch);
873 if(!user) {
874 Nitdoc.GitHub.ModalBox.open("Sign in error", "The username, password, repo or branch you entered is incorrect.", true);
875 } else {
876 Nitdoc.GitHub.UI.activate(user);
877 var origin = Nitdoc.GitHub.UI.getOrigin();
878 Nitdoc.GitHub.LoginBox.displayLogout(origin, user);
879 }
880 }
881 return false;
882 })
883 )
884 $(".nitdoc-github-li-img").attr("src", "resources/icons/github-icon.png");
885 loginBoxContent.empty()
886 loginBoxContent.append(panel);
887 }
888
889 var toggle = function() {
890 if(loginBox.is(':hidden')) {
891 loginBox.show();
892 if (!$('#loginGit').is(':hidden')) { $('#loginGit').focus(); }
893 } else {
894 loginBox.hide();
895 }
896 }
897
898 // Public interface
899 var loginbox = {
900 init: init,
901 displayLogin: displayLogin,
902 displayLogout: displayLogout,
903 toggle: toggle,
904
905 };
906
907 return loginbox;
908 }();
909
910 /*
911 * Nitdoc.GitHub.CommentBox class
912 */
913
914 // Init new modal box instance
915 Nitdoc.GitHub.CommentBox = function(infos) {
916 this.infos = infos;
917 this.commentBoxDiv;
918 }
919
920 Nitdoc.GitHub.CommentBox.prototype.open = function(baseArea) {
921 Nitdoc.GitHub.UI.addOpenedComments();
922 var instance = this;
923
924 if(this.infos.requestID) {
925 // get comment from last pull request
926 var requests = JSON.parse(localStorage.requests);
927 this.infos.newComment = Base64.decode(requests[this.infos.requestID].comment);
928 } else {
929 this.infos.newComment = false;
930 }
931
932 // create comment box
933 var tarea = $(document.createElement("textarea"))
934 .append(this.infos.newComment === false? this.infos.oldComment: this.infos.newComment)
935 .keyup(function(event) {
936 $(event.target).css("height", (event.target.value.split(/\r|\n/).length * 16) + "px");
937 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) ) {
938 $(event.target).parent().find("button.nitdoc-github-commit").removeAttr("disabled");
939 } else {
940 $(event.target).parent().find("button.nitdoc-github-commit").attr("disabled", "disabled");
941 }
942 })
943 .keydown(function(event) {
944 if(event.keyCode == 13){
945 $(event.target).css("height", ($(event.target).outerHeight() + 6) + "px");
946 }
947 });
948
949 this.commentBoxDiv = $(document.createElement("div"))
950 .addClass("nitdoc-github-commentbox")
951 .append(tarea)
952 .append(
953 $(document.createElement("a"))
954 .addClass("nitdoc-github-preview")
955 .click(function() {
956 var converter = new Markdown.Converter()
957 var html = converter.makeHtml(tarea.val());
958 Nitdoc.GitHub.ModalBox.open("Preview", html, false);
959 })
960 )
961 .append(
962 $(document.createElement("button"))
963 .addClass("nitdoc-github-button")
964 .addClass("nitdoc-github-commit")
965 .append("Commit")
966 .click(function() {
967 instance.infos.newComment = tarea.val();
968 instance.infos.commentBox = instance;
969 Nitdoc.GitHub.CommitBox.open(instance.infos);
970 })
971 )
972 .append(
973 $(document.createElement("button"))
974 .addClass("nitdoc-github-button")
975 .addClass("nitdoc-github-cancel")
976 .append("Cancel")
977 .click(function() {instance.close()})
978 );
979
980 baseArea.after(this.commentBoxDiv);
981 var cbWidth = this.commentBoxDiv.innerWidth();
982 var taWidth = tarea.outerWidth();
983 tarea.width(cbWidth - (taWidth - cbWidth));
984 tarea.trigger("keyup");
985 tarea.focus();
986 }
987
988 Nitdoc.GitHub.CommentBox.prototype.close = function() {
989 Nitdoc.GitHub.UI.remOpenedComments();
990 if(this.infos.isNew) {
991 this.commentBoxDiv.next().find("span.nitdoc-github-editComment").show();
992 } else if(this.infos.requestID) {
993 this.commentBoxDiv.next().show();
994 this.commentBoxDiv.next().next().show();
995 } else {
996 this.commentBoxDiv.next().show();
997 this.commentBoxDiv.next().next().find("span.nitdoc-github-editComment").show();
998 }
999 this.commentBoxDiv.remove();
1000 }
1001
1002 /*
1003 * Nitdoc.GitHub.ModalBox class
1004 */
1005
1006 // Init new modal box instance
1007 Nitdoc.GitHub.ModalBox = function() {
1008
1009 // Open modal box instance
1010 var open = function(title, content, isError) {
1011 $("body").append(
1012 $(document.createElement("div"))
1013 .attr("id", "nitdoc-github-modal-fade")
1014 .addClass("nitdoc-github-fade")
1015 )
1016 .append(
1017 $(document.createElement("div"))
1018 .attr("id", "nitdoc-github-modal")
1019 .addClass("nitdoc-github-modal")
1020 .append(
1021 $(document.createElement("a"))
1022 .addClass("nitdoc-github-close")
1023 .append(
1024 $(document.createElement("img"))
1025 .addClass("nitdoc-github-imgClose")
1026 .attr({
1027 src: "resources/icons/close.png",
1028 title: "Close",
1029 alt: "Close"
1030
1031 })
1032 ).click(function() { Nitdoc.GitHub.ModalBox.close() })
1033 )
1034 .append("<h3>" + title + "</h3>")
1035 .append("<div>" + content + "</div>")
1036 .append(
1037 $(document.createElement("div"))
1038 .addClass("nitdoc-github-buttons")
1039 .append(
1040 $(document.createElement("button"))
1041 .addClass("nitdoc-github-button")
1042 .append("Ok")
1043 .click(function() { Nitdoc.GitHub.ModalBox.close() })
1044 )
1045 )
1046 );
1047
1048 if(isError) {
1049 $("#nitdoc-github-modal").addClass("nitdoc-github-error");
1050 }
1051
1052 $("#nitdoc-github-modal").css({
1053 top: "50%",
1054 marginTop: -($("#nitdoc-github-modal").outerHeight() / 2) + "px",
1055 left: "50%",
1056 marginLeft: -($("#nitdoc-github-modal").outerWidth() / 2) + "px"
1057 });
1058 }
1059
1060 // Close modal box instance
1061 var close = function() {
1062 $("#nitdoc-github-modal").remove();
1063 $("#nitdoc-github-modal-fade").remove();
1064 }
1065
1066 // Public interface
1067 var modalBox = {
1068 open: open,
1069 close: close
1070 };
1071
1072 return modalBox;
1073 }();
1074
1075 /*
1076 * Nitdoc.GitHub.CommitBox instance
1077 */
1078
1079 // Init new commit box instance
1080 Nitdoc.GitHub.CommitBox = function() {
1081
1082 // Open commit box instance
1083 var open = function(infos) {
1084 $("body").append(
1085 $(document.createElement("div"))
1086 .attr("id", "nitdoc-github-commitBox-fade")
1087 .addClass("nitdoc-github-fade")
1088 );
1089 $("body").append(
1090 $(document.createElement("div"))
1091 .attr("id", "nitdoc-github-commitBox")
1092 .addClass("nitdoc-github-modal")
1093 .append(
1094 $(document.createElement("a"))
1095 .addClass("nitdoc-github-close")
1096 .append(
1097 $(document.createElement("img"))
1098 .addClass("nitdoc-github-imgClose")
1099 .attr({
1100 src: "resources/icons/close.png",
1101 title: "Close",
1102 alt: "Close"
1103
1104 })
1105 ).click(function() { Nitdoc.GitHub.CommitBox.close() })
1106 )
1107 .append("<h3>Commit changes</h3>")
1108 .append(
1109 $(document.createElement("div"))
1110 .append(
1111 $(document.createElement("label"))
1112 .attr("for", "nitdoc-github-commit-message")
1113 )
1114 .append("<br/>")
1115 .append(
1116 $(document.createElement("textarea"))
1117 .attr("id", "nitdoc-github-commit-message")
1118 .append("doc: " + (infos.isNew ? "added" : "modified") + " comment for " + infos.namespace)
1119 )
1120 .append("<br/>")
1121 .append(
1122 $(document.createElement("input"))
1123 .attr({
1124 id: "nitdoc-github-commit-signedoff",
1125 type: "checkbox",
1126 value: "Signed-off-by: " + infos.user.signedOff
1127 })
1128 .change(function(e) {
1129 if ($(this).is(':checked')) {
1130 $("#nitdoc-github-commit-button").removeAttr("disabled");
1131 } else {
1132 $("#nitdoc-github-commit-button").attr("disabled", "disabled");
1133 }
1134 })
1135 )
1136 .append(
1137 $(document.createElement("label"))
1138 .attr("for", "nitdoc-github-commit-signedoff")
1139 .append("Signed-off-by: " + infos.user.signedOff)
1140 )
1141 ).append(
1142 $(document.createElement("div"))
1143 .addClass("nitdoc-github-buttons")
1144 .append(
1145 $(document.createElement("button"))
1146 .attr({
1147 id: "nitdoc-github-commit-button",
1148 disabled: "disabled"
1149 })
1150 .addClass("nitdoc-github-button")
1151 .append(
1152 $(document.createElement("img"))
1153 .attr("src", "resources/icons/github-icon.png")
1154 )
1155 .append("Commit")
1156 .mousedown(function() {
1157 $(this).text("Commiting...");
1158 })
1159 .mouseup(function() {
1160 infos.message = $("#nitdoc-github-commit-message").val() + "\n\n" + infos.user.signedOff;
1161 Nitdoc.GitHub.UI.saveChanges(infos);
1162 })
1163 )
1164 )
1165 );
1166
1167 $("#nitdoc-github-commitBox").css({
1168 top: "50%",
1169 marginTop: -($("#nitdoc-github-commitBox").outerHeight() / 2) + "px",
1170 left: "50%",
1171 marginLeft: -($("#nitdoc-github-commitBox").outerWidth() / 2) + "px"
1172 });
1173 }
1174
1175 // Close commit box instance
1176 var close = function() {
1177 $("#nitdoc-github-commitBox").remove();
1178 $("#nitdoc-github-commitBox-fade").remove();
1179 }
1180
1181 // Public interface
1182 var commitBox = {
1183 open: open,
1184 close: close
1185 }
1186 return commitBox;
1187 }();
1188
1189 /*
1190 * Nitdoc.GitHub.Utils module
1191 */
1192
1193 Nitdoc.GitHub.Utils = function() {
1194 // Extract infos from string location "../lib/standard/collection/array.nit:457,1--458,0"
1195 //FIXME this should be done by nitdoc
1196 var parseLocation = function(location) {
1197 var parts = location.split(":");
1198 var loc = new Object();
1199 loc.origin = location;
1200 loc.path = parts[0].substr(3, parts[0].length);
1201 loc.lstart = parseInt(parts[1].split("--")[0].split(",")[0]);
1202 loc.tabpos = parseInt(parts[1].split("--")[0].split(",")[1]);
1203 loc.lend = parseInt(parts[1].split("--")[1].split(",")[0]);
1204 return loc;
1205 }
1206
1207 // Meld modified comment into file content
1208 var mergeComment = function(fileContent, comment, location) {
1209 // replace comment in file content
1210 var res = new String();
1211 var lines = fileContent.split("\n");
1212 // copy lines fron 0 to lstart
1213 for(var i = 0; i < location.lstart - 1; i++) {
1214 res += lines[i] + "\n";
1215 }
1216 // set comment
1217 if(comment && comment != "") {
1218 var commentLines = comment.split("\n");
1219 for(var i = 0; i < commentLines.length; i++) {
1220 var line = commentLines[i];
1221 var tab = location.tabpos > 1 ? "\t" : "";
1222 res += tab + (line.length > 0 ? "# " : "#") + line + "\n";
1223 }
1224 }
1225 // copy lines fron lend to end
1226 for(var i = location.lend - 1; i < lines.length; i++) {
1227 res += lines[i];
1228 if(i < lines.length - 1) { res += "\n"; }
1229 }
1230 return res;
1231 }
1232
1233 // Public interface
1234 var utils = {
1235 parseLocation: parseLocation,
1236 mergeComment: mergeComment
1237 };
1238
1239 return utils;
1240 }();
1241