Created
October 28, 2008 15:44
-
-
Save vic/20406 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name Gmail w/ Collapsible & Minimalist Inbox plus Google Calendar, Reader, Notebook, etc... Seamless Integration | |
| // @namespace mail.google.com | |
| // @include http://mail.google.com/* | |
| // @include https://mail.google.com/* | |
| // ==/UserScript== | |
| var USER_CONFIGURATION = | |
| { | |
| ELEMENTS: //Place in order the pages you would like to load. Placing mail in the list will may it collapsible. Other choices are: reader, calendar, notebook or groups (must be lowercase.) | |
| { | |
| FIRST:"mail", | |
| SECOND:"feedly", | |
| THRIRD:"groups", | |
| FOURTH:"calendar", | |
| FIFTH:"notebook", | |
| SIXTH:"", | |
| SEVENTH: "", | |
| EIGHTH: "", | |
| NINTH: "" | |
| }, | |
| MAIL: | |
| { | |
| BORDER_AROUND_THREADBOX: false, //Creates a white border around the inbox | |
| ALLOW_STARTING_COLLAPSED: true, | |
| ENABLE_MINIMALIST_THREADBOX: true //Enable this if you keep a small and clean inbox | |
| }, | |
| CALENDAR: | |
| { | |
| FRAME_HEIGHT: "500px", | |
| ALLOW_STARTING_COLLAPSED: true //May cause unviewable text in 2+ week and month views. | |
| }, | |
| FEEDLY: | |
| { | |
| FRAME_HEIGHT: "500px", | |
| ALLOW_STARTING_COLLAPSED: true | |
| }, | |
| READER: | |
| { | |
| FRAME_HEIGHT: "500px", | |
| ALLOW_STARTING_COLLAPSED: true | |
| }, | |
| NOTEBOOK: | |
| { | |
| FRAME_HEIGHT: "500px", | |
| ALLOW_STARTING_COLLAPSED: false //May cause a background constant loading until expanded. | |
| }, | |
| GROUPS: | |
| { | |
| FRAME_HEIGHT: "500px", | |
| ALLOW_STARTING_COLLAPSED: true | |
| }, | |
| MAPS: | |
| { | |
| FRAME_HEIGHT: "500px", | |
| ALLOW_STARTING_COLLAPSED: true | |
| }, | |
| PICASA: | |
| { | |
| FRAME_HEIGHT: "500px", | |
| ALLOW_STARTING_COLLAPSED: true | |
| }, | |
| SITES: | |
| { | |
| FRAME_HEIGHT: "500px", | |
| ALLOW_STARTING_COLLAPSED: true | |
| }, | |
| NEWS: | |
| { | |
| FRAME_HEIGHT: "500px", | |
| ALLOW_STARTING_COLLAPSED: true | |
| }, | |
| PORTFOLIO: | |
| { | |
| FRAME_HEIGHT: "500px", | |
| ALLOW_STARTING_COLLAPSED: true | |
| } | |
| }; | |
| var GLOBAL_CONFIGURATION = //DO NOT EDIT | |
| { | |
| MAIL: | |
| { | |
| NAME: "Mail" | |
| }, | |
| CALENDAR: | |
| { | |
| NAME: "Calendar", | |
| URL_STD: "https://www.google.com/calendar", | |
| URL_APP: "http://www.google.com/calendar/hosted/" | |
| }, | |
| FEEDLY: | |
| { | |
| NAME: "Feedly", | |
| URL_STD: "http://www.google.com", | |
| URL_APP: "" | |
| }, | |
| READER: | |
| { | |
| NAME: "Reader", | |
| URL_STD: "https://www.google.com/reader", | |
| URL_APP: "" | |
| }, | |
| NOTEBOOK: | |
| { | |
| NAME: "Notebook", | |
| URL_STD: "https://www.google.com/notebook", | |
| URL_APP: "" | |
| }, | |
| GROUPS: | |
| { | |
| NAME: "Groups", | |
| URL_STD: "https://groups.google.com/", | |
| URL_APP: "" | |
| }, | |
| MAPS: | |
| { | |
| NAME: "Maps", | |
| URL_STD: "http://maps.google.com/maps?hl=en&tab=wl", | |
| URL_APP: "" | |
| }, | |
| PICASA: | |
| { | |
| NAME: "Picasa", | |
| URL_STD: "http://picasaweb.google.com/home?hl=en&tab=wq", | |
| URL_APP: "" | |
| }, | |
| SITES: | |
| { | |
| NAME: "Sites", | |
| URL_STD: "https://sites.google.com/?tab=m3&pli=1", | |
| URL_APP: "" | |
| }, | |
| NEWS: | |
| { | |
| NAME: "News", | |
| URL_STD: "http://news.google.com/nwshp?tab=mn", | |
| URL_APP: "" | |
| }, | |
| PORTFOLIO: | |
| { | |
| NAME: "Portfolio", | |
| URL_STD: "http://finance.google.com/finance/portfolio?action=view&pid=1", | |
| URL_APP: "" | |
| }, | |
| ELEMENTS: new Array(9) | |
| }; | |
| function onPageLoaded() | |
| { | |
| if(unsafeWindow.gmonkey) unsafeWindow.gmonkey.load('1.0', function(gmail_api) {checkGMailAPILoaded(gmail_api)}); | |
| } | |
| function checkGMailAPILoaded(gmail_api) | |
| { | |
| if(!unsafeWindow.gmonkey.isLoaded('1.0')) {setTimeout(checkGMailAPILoaded,200); return;} | |
| determineElement(gmail_api, USER_CONFIGURATION.ELEMENTS.FIRST, 0); | |
| determineElement(gmail_api, USER_CONFIGURATION.ELEMENTS.SECOND, 1); | |
| determineElement(gmail_api, USER_CONFIGURATION.ELEMENTS.THRIRD, 2); | |
| determineElement(gmail_api, USER_CONFIGURATION.ELEMENTS.FOURTH, 3); | |
| determineElement(gmail_api, USER_CONFIGURATION.ELEMENTS.FIFTH, 4); | |
| determineElement(gmail_api, USER_CONFIGURATION.ELEMENTS.SIXTH, 5); | |
| determineElement(gmail_api, USER_CONFIGURATION.ELEMENTS.SEVENTH, 6); | |
| determineElement(gmail_api, USER_CONFIGURATION.ELEMENTS.EIGHTH, 7); | |
| determineElement(gmail_api, USER_CONFIGURATION.ELEMENTS.NINTH, 8); | |
| orderElementBoxes(gmail_api); | |
| } | |
| function determineElement(gmail_api, element_string, element_position) | |
| { | |
| switch (element_string.toLowerCase()) | |
| { | |
| case 'mail': GLOBAL_CONFIGURATION.MAIL.POSITION = element_position; loadElement(gmail_api, USER_CONFIGURATION.MAIL, GLOBAL_CONFIGURATION.MAIL); break; | |
| case 'calendar': GLOBAL_CONFIGURATION.CALENDAR.POSITION = element_position; loadElement(gmail_api, USER_CONFIGURATION.CALENDAR, GLOBAL_CONFIGURATION.CALENDAR); break; | |
| case 'feedly': GLOBAL_CONFIGURATION.FEEDLY.POSITION = element_position; loadElement(gmail_api, USER_CONFIGURATION.FEEDLY, GLOBAL_CONFIGURATION.FEEDLY); break; | |
| case 'reader': GLOBAL_CONFIGURATION.READER.POSITION = element_position; loadElement(gmail_api, USER_CONFIGURATION.READER, GLOBAL_CONFIGURATION.READER); break; | |
| case 'notebook': GLOBAL_CONFIGURATION.NOTEBOOK.POSITION = element_position; loadElement(gmail_api, USER_CONFIGURATION.NOTEBOOK, GLOBAL_CONFIGURATION.NOTEBOOK); break; | |
| case 'groups': GLOBAL_CONFIGURATION.GROUPS.POSITION = element_position; loadElement(gmail_api, USER_CONFIGURATION.GROUPS, GLOBAL_CONFIGURATION.GROUPS); break; | |
| case 'maps': GLOBAL_CONFIGURATION.MAPS.POSITION = element_position; loadElement(gmail_api, USER_CONFIGURATION.MAPS, GLOBAL_CONFIGURATION.MAPS); break; | |
| case 'picasa': GLOBAL_CONFIGURATION.PICASA.POSITION = element_position; loadElement(gmail_api, USER_CONFIGURATION.PICASA, GLOBAL_CONFIGURATION.PICASA); break; | |
| case 'sites': GLOBAL_CONFIGURATION.SITES.POSITION = element_position; loadElement(gmail_api, USER_CONFIGURATION.SITES, GLOBAL_CONFIGURATION.SITES); break; | |
| case 'news': GLOBAL_CONFIGURATION.NEWS.POSITION = element_position; loadElement(gmail_api, USER_CONFIGURATION.NEWS, GLOBAL_CONFIGURATION.NEWS); break; | |
| case 'portfolio': GLOBAL_CONFIGURATION.PORTFOLIO.POSITION = element_position; loadElement(gmail_api, USER_CONFIGURATION.PORTFOLIO, GLOBAL_CONFIGURATION.PORTFOLIO); break; | |
| } | |
| } | |
| function loadElement(gmail_api, user_configuration, global_configuration) | |
| { | |
| if(user_configuration.ALLOW_STARTING_COLLAPSED) GLOBAL_CONFIGURATION.ELEMENTS[global_configuration.POSITION] = gmail_api.addNavModule('<span id="'+global_configuration.NAME.toLowerCase()+'Box">'+global_configuration.NAME+'</span>'); | |
| else GLOBAL_CONFIGURATION.ELEMENTS[global_configuration.POSITION] = gmail_api.addNavModule('<span id="'+global_configuration.NAME.toLowerCase()+'Box">'+global_configuration.NAME+'<span style="display:none;">'+(new Date()).getTime()+'</span></span>'); | |
| if(global_configuration.URL_STD != null) GLOBAL_CONFIGURATION.ELEMENTS[global_configuration.POSITION].setContent("<iframe id='"+global_configuration.NAME.toLowerCase()+"_iframe' style='border: medium none ; padding: 2px; display:block; width:100%;'></iframe>"); | |
| else GLOBAL_CONFIGURATION.ELEMENTS[global_configuration.POSITION].setContent("<div id='"+global_configuration.NAME.toLowerCase()+"_div'><div id='placeHolder'></div></div>"); | |
| checkContentsLoaded(gmail_api, user_configuration, global_configuration) | |
| } | |
| function checkContentsLoaded(gmail_api, user_configuration, global_configuration) | |
| { | |
| var elementLoaded = parent.document.getElementById("canvas_frame").contentDocument.getElementById(global_configuration.NAME.toLowerCase()+"BoxLoaded"); | |
| try {if(typeof gmail_api.getFooterElement() != 'undefined' && typeof GLOBAL_CONFIGURATION.ELEMENTS[global_configuration.POSITION].getElement() != 'undefined' && elementLoaded == null) loadContents(gmail_api, user_configuration, global_configuration);} | |
| catch(err) {setTimeout(function(){checkContentsLoaded(gmail_api, user_configuration, global_configuration);},200);} | |
| } | |
| function loadContents(gmail_api, user_configuration, global_configuration) | |
| { | |
| gmail_api.getFooterElement().insertBefore(GLOBAL_CONFIGURATION.ELEMENTS[global_configuration.POSITION].getElement(), gmail_api.getFooterElement().childNodes[0]); | |
| var elementBoxDOM = parent.document.getElementById("canvas_frame").contentDocument.getElementById(global_configuration.NAME.toLowerCase()+"Box"); | |
| elementBoxDOM.id = global_configuration.NAME.toLowerCase()+"BoxLoaded"; | |
| elementBoxDOM = elementBoxDOM.parentNode.parentNode; | |
| elementBoxDOM.style.textAlign = "left"; | |
| for(var i = 0; i < 11; i++) elementBoxDOM = elementBoxDOM.parentNode; | |
| elementBoxDOM.style.width = "100%"; | |
| if(global_configuration.URL_STD != null) | |
| { | |
| var detectedURL = detectURL(global_configuration); | |
| if(detectedURL == "HIDE") {elementBoxDOM.style.display = "none"; return;} | |
| var elementBoxIFrame = parent.document.getElementById("canvas_frame").contentDocument.getElementById(global_configuration.NAME.toLowerCase()+"_iframe"); | |
| elementBoxIFrame.src = 'null'; | |
| elementBoxIFrame.src = detectedURL; | |
| elementBoxIFrame.style.height = user_configuration.FRAME_HEIGHT; | |
| } | |
| else COLLAPSIBLE_THREADBOX.loadContent(gmail_api, user_configuration, global_configuration); | |
| } | |
| function detectURL(global_configuration) | |
| { | |
| if(document.URL.search("mail.google.com/a") != -1) | |
| { | |
| if(global_configuration.URL_APP == "") return "HIDE"; | |
| var domain = document.URL.substring(document.URL.search("mail.google.com/a/")+18); | |
| domain = domain.substring(0,domain.search("/")); | |
| return global_configuration.URL_APP+domain; | |
| } | |
| else return global_configuration.URL_STD; | |
| } | |
| function orderElementBoxes(gmail_api) | |
| { | |
| try{gmail_api.getFooterElement();} | |
| catch(err) {setTimeout(function(){orderElementBoxes(gmail_api);},200); return;} | |
| for(var i = GLOBAL_CONFIGURATION.ELEMENTS.length; i >=0; i--) | |
| { | |
| if(GLOBAL_CONFIGURATION.ELEMENTS[i] == null) continue; | |
| var place_before_id = "ATBOTTOM"; | |
| for(var j = 1; i+j < GLOBAL_CONFIGURATION.ELEMENTS.length && place_before_id == "ATBOTTOM"; j++) | |
| { | |
| if(GLOBAL_CONFIGURATION.MAIL.POSITION == i+j) place_before_id = GLOBAL_CONFIGURATION.MAIL.NAME.toLowerCase() + "BoxLoaded"; | |
| else if(GLOBAL_CONFIGURATION.CALENDAR.POSITION == i+j) place_before_id = GLOBAL_CONFIGURATION.CALENDAR.NAME.toLowerCase() + "BoxLoaded"; | |
| else if(GLOBAL_CONFIGURATION.FEEDLY.POSITION == i+j) place_before_id = GLOBAL_CONFIGURATION.FEEDLY.NAME.toLowerCase() + "BoxLoaded"; | |
| else if(GLOBAL_CONFIGURATION.READER.POSITION == i+j) place_before_id = GLOBAL_CONFIGURATION.READER.NAME.toLowerCase() + "BoxLoaded"; | |
| else if(GLOBAL_CONFIGURATION.NOTEBOOK.POSITION == i+j) place_before_id = GLOBAL_CONFIGURATION.NOTEBOOK.NAME.toLowerCase() + "BoxLoaded"; | |
| else if(GLOBAL_CONFIGURATION.GROUPS.POSITION == i+j) place_before_id = GLOBAL_CONFIGURATION.GROUPS.NAME.toLowerCase() + "BoxLoaded"; | |
| else if(GLOBAL_CONFIGURATION.MAPS.POSITION == i+j) place_before_id = GLOBAL_CONFIGURATION.MAPS.NAME.toLowerCase() + "BoxLoaded"; | |
| else if(GLOBAL_CONFIGURATION.PICASA.POSITION == i+j) place_before_id = GLOBAL_CONFIGURATION.PICASA.NAME.toLowerCase() + "BoxLoaded"; | |
| else if(GLOBAL_CONFIGURATION.SITES.POSITION == i+j) place_before_id = GLOBAL_CONFIGURATION.SITES.NAME.toLowerCase() + "BoxLoaded"; | |
| else if(GLOBAL_CONFIGURATION.NEWS.POSITION == i+j) place_before_id = GLOBAL_CONFIGURATION.NEWS.NAME.toLowerCase() + "BoxLoaded"; | |
| else if(GLOBAL_CONFIGURATION.PORTFOLIO.POSITION == i+j) place_before_id = GLOBAL_CONFIGURATION.PORTFOLIO.NAME.toLowerCase() + "BoxLoaded"; | |
| } | |
| if(place_before_id.search("ATBOTTOM") == -1) | |
| { | |
| var placeBeforeBox = parent.document.getElementById("canvas_frame").contentDocument.getElementById(place_before_id); | |
| if(placeBeforeBox == null) {setTimeout(function(){orderElementBoxes(gmail_api);},200); return;} | |
| for(var k = 0; k < 13; k++) placeBeforeBox = placeBeforeBox.parentNode; | |
| gmail_api.getFooterElement().insertBefore(GLOBAL_CONFIGURATION.ELEMENTS[i].getElement(),placeBeforeBox); | |
| } | |
| else gmail_api.getFooterElement().insertBefore(GLOBAL_CONFIGURATION.ELEMENTS[i].getElement(),gmail_api.getFooterElement().childNodes[gmail_api.getFooterElement().childNodes.length-1]); | |
| } | |
| } | |
| function addIFrameStyle(css, iframeID) | |
| { | |
| var head = parent.document.getElementById(iframeID).contentDocument.getElementsByTagName('head')[0]; | |
| if (!head) { return; } | |
| var style = document.createElement('style'); | |
| style.type = 'text/css'; | |
| style.innerHTML = css; | |
| head.appendChild(style); | |
| } | |
| var COLLAPSIBLE_THREADBOX = | |
| { | |
| loadContent: function(gmail_api, user_configuration, global_configuration) | |
| { | |
| var threadBoxDiv = parent.document.getElementById("canvas_frame").contentDocument.getElementById(global_configuration.NAME.toLowerCase()+"_div"); | |
| if(user_configuration.BORDER_AROUND_THREADBOX) threadBoxDiv.style.padding = "5px"; | |
| else threadBoxDiv.style.padding = "-15px"; | |
| COLLAPSIBLE_THREADBOX.fillContent(gmail_api, threadBoxDiv); | |
| COLLAPSIBLE_THREADBOX.redrawNavList(gmail_api, global_configuration); | |
| if(user_configuration.ENABLE_MINIMALIST_THREADBOX) MINIMALIST_THREADBOX.makeMinimalist(gmail_api); | |
| setTimeout(function(){COLLAPSIBLE_THREADBOX.resizeConversation(gmail_api,global_configuration,false);},500); | |
| gmail_api.registerViewChangeCallback(function(){COLLAPSIBLE_THREADBOX.resizeConversation(gmail_api,global_configuration,false);}); | |
| window.parent.addEventListener('resize',function(){COLLAPSIBLE_THREADBOX.resizeConversation(gmail_api,global_configuration,true);},false); | |
| }, | |
| fillContent: function(gmail_api, threadBoxDiv) | |
| { | |
| var placeHolder = parent.document.getElementById("canvas_frame").contentDocument.getElementById("placeHolder"); | |
| var activeView = gmail_api.getActiveViewElement() | |
| for(var i = 0; i < 9; i++) activeView = activeView.parentNode; | |
| threadBoxDiv.insertBefore(activeView, placeHolder); | |
| }, | |
| redrawNavList: function(gmail_api, global_configuration) | |
| { | |
| COLLAPSIBLE_THREADBOX.findTopRightCurlClass(global_configuration); | |
| COLLAPSIBLE_THREADBOX.redrawListItem(gmail_api.getNavPaneElement().childNodes[0].childNodes[0],global_configuration); | |
| COLLAPSIBLE_THREADBOX.redrawListItem(gmail_api.getNavPaneElement().childNodes[0].childNodes[1].childNodes[0].childNodes[0].childNodes[0].childNodes[0].childNodes[0].childNodes[0],global_configuration); | |
| COLLAPSIBLE_THREADBOX.redrawListItem(gmail_api.getNavPaneElement().childNodes[0].childNodes[1].childNodes[0].childNodes[0].childNodes[0].childNodes[0].childNodes[0].childNodes[1],global_configuration); | |
| COLLAPSIBLE_THREADBOX.redrawListItem(gmail_api.getNavPaneElement().childNodes[0].childNodes[1].childNodes[0].childNodes[0].childNodes[0].childNodes[0].childNodes[0].childNodes[2],global_configuration); | |
| COLLAPSIBLE_THREADBOX.redrawListItem(gmail_api.getNavPaneElement().childNodes[0].childNodes[1].childNodes[0].childNodes[0].childNodes[0].childNodes[0].childNodes[0].childNodes[3],global_configuration); | |
| COLLAPSIBLE_THREADBOX.redrawListItem(gmail_api.getNavPaneElement().childNodes[0].childNodes[1].childNodes[0].childNodes[0].childNodes[0].childNodes[0].childNodes[0].childNodes[4],global_configuration); | |
| COLLAPSIBLE_THREADBOX.redrawListItem(gmail_api.getNavPaneElement().childNodes[0].childNodes[1].childNodes[0].childNodes[0].childNodes[0].childNodes[0].childNodes[0].childNodes[5],global_configuration); | |
| COLLAPSIBLE_THREADBOX.redrawListItem(gmail_api.getNavPaneElement().childNodes[0].childNodes[1].childNodes[0].childNodes[0].childNodes[0].childNodes[0].childNodes[0].childNodes[6],global_configuration); | |
| COLLAPSIBLE_THREADBOX.redrawListItem(gmail_api.getNavPaneElement().childNodes[0].childNodes[1].childNodes[0].childNodes[0].childNodes[0].childNodes[0].childNodes[0].childNodes[7],global_configuration); | |
| COLLAPSIBLE_THREADBOX.redrawListItem(gmail_api.getNavPaneElement().childNodes[0].childNodes[1].childNodes[0].childNodes[0].childNodes[0].childNodes[2],global_configuration); | |
| }, | |
| findTopRightCurlClass: function(global_configuration) | |
| { | |
| global_configuration.topRightCurlClass = GLOBAL_CONFIGURATION.ELEMENTS[global_configuration.POSITION].getElement().childNodes[0].childNodes[0].childNodes[0].childNodes[0].className.split(" ")[1]; | |
| }, | |
| redrawListItem: function(item, global_configuration) | |
| { | |
| item.style.width = "90%"; | |
| item = item.childNodes[0].childNodes[0].childNodes[0]; | |
| item.className = item.className + " " + global_configuration.topRightCurlClass; | |
| item = item.childNodes[1]; | |
| item.style.width = "3px"; | |
| item = item.parentNode.parentNode.childNodes[3].childNodes[0]; | |
| item.style.width = "3px"; | |
| }, | |
| resizeConversation: function(gmail_api, global_configuration, windowResized) | |
| { | |
| if(gmail_api.getActiveViewType() != 'cv') return; | |
| var messageBox = parent.document.getElementById("canvas_frame").contentDocument.getElementById(global_configuration.NAME.toLowerCase()+"_div").getElementsByTagName("table"); | |
| for(var i = 0; i < messageBox.length; i++) {if(messageBox[i].className.split(" ").length > 2) { messageBox = messageBox[i].childNodes[0].childNodes[0].childNodes[0]; break;}} | |
| if(messageBox.style.width.split("px").length <= 1 || (messageBox.id == "resizedMessageBox" && !windowResized)) {setTimeout(function(){COLLAPSIBLE_THREADBOX.resizeConversation(gmail_api,global_configuration,windowResized)},100); return;} | |
| messageBox.id = "resizedMessageBox"; | |
| messageBox.style.width = (messageBox.style.width.split("px")[0]-50)+"px"; | |
| } | |
| }; | |
| var MINIMALIST_THREADBOX = | |
| { | |
| makeMinimalist: function(gmail_api) | |
| { | |
| MINIMALIST_THREADBOX.hideNullMessages(); | |
| MINIMALIST_THREADBOX.hideLowerMessageOptions(gmail_api); | |
| gmail_api.registerViewChangeCallback(function(){MINIMALIST_THREADBOX.hideLowerMessageOptions(gmail_api);}); | |
| }, | |
| hideNullMessages: function() | |
| { | |
| addIFrameStyle(".lW3BFe {display: none; !important};","canvas_frame"); | |
| }, | |
| hideLowerMessageOptions: function(gmail_api) | |
| { | |
| if(gmail_api.getActiveViewType() != 'tl') return; | |
| var spanList = parent.document.getElementById("canvas_frame").contentDocument.getElementsByTagName("span"); | |
| for(var i = spanList.length-1; i >= 0; i--) {if(spanList[i].getAttribute("role") == "link" && spanList[i].innerHTML == "All") {var lowerMessageOptionsDiv = spanList[i]; break;}} | |
| lowerMessageOptionsDiv = lowerMessageOptionsDiv.parentNode.parentNode.parentNode.parentNode.parentNode; | |
| lowerMessageOptionsDiv.style.display = "none"; | |
| } | |
| }; | |
| window.addEventListener('load', onPageLoaded, true); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment