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