diff --git a/css/style.css b/css/style.css index 41db137d..810c9dd6 100644 --- a/css/style.css +++ b/css/style.css @@ -1,59 +1,183 @@ +/* General Reset */ +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} +body { + font-family: "Lato", sans-serif; + background-color: #f5f7fa; + color: #333; +} -.inline-block { -display: inline; +/* Top Label */ +.TopLabel { + font-weight: bold; + font-size: 1.5rem; + margin: 1rem; } -.table { -background: orange; -width: 90%; -text-align: left; -margin: auto; +/* Container Layout */ +.search-body { + background-color: #fff; + border-radius: 12px; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.07); + padding: 2rem; + margin: 2rem auto; + max-width: 1100px; } +/* Spinner Styling */ .spinner { display: none; position: absolute; left: 50%; top: 50%; - z-index: 1; - width: 150px; - height: 150px; - margin: -75px 0 0 -75px; - border: 16px solid #f3f3f3; + z-index: 10; + transform: translate(-50%, -50%); + width: 80px; + height: 80px; + border: 8px solid #f3f3f3; + border-top: 8px solid #7057ff; border-radius: 50%; - border-top: 16px solid #3498db; - width: 120px; - height: 120px; - -webkit-animation: spin 2s linear infinite; - animation: spin 2s linear infinite; + animation: spin 1s linear infinite; +} + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +/* Issue Label Tags */ +.IssueLabel { + padding: 0.25rem 0.75rem; + font-size: 0.85rem; + font-weight: 600; + border-radius: 4px; + display: inline-block; + margin-right: 0.5rem; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); +} + +.GoodFirstIssue { + background-color: #7057ff; + color: #fff; +} + +.StarterTask { + background-color: #62dbc9; + color: #000; } +.HelpWanted { + background-color: #008672; + color: #fff; +} + +/* Language Form */ +.form-header { + margin-bottom: 1.5rem; +} + +.LanguageOptions { + min-width: 220px; + border-radius: 6px; + padding: 0.5rem; + border: 1px solid #ccc; +} + +/* Table Styling */ +.table-responsive { + overflow-x: auto; +} + +.table { + width: 100%; + margin-top: 1rem; + border-collapse: collapse; + border-radius: 8px; + overflow: hidden; + background-color: #fff; +} -@-webkit-keyframes spin { - 0% { -webkit-transform: rotate(0deg); } - 100% { -webkit-transform: rotate(360deg); } +.table th, +.table td { + padding: 1rem; + text-align: left; + border-bottom: 1px solid #f0f0f0; } +.table thead { + background-color: #f8f9fa; + font-weight: 600; +} + +.table th.sortsTable:hover { + color: #7057ff; + cursor: pointer; +} + +/* Utility Classes */ +.hidden { + display: none !important; +} + +.QuestionsSuggestions { + font-size: 0.875rem; + color: #666; + margin: 1.5rem 0; + text-align: center; +} + +.QuestionsSuggestions a { + color: #0073e6; + text-decoration: none; +} + +.QuestionsSuggestions a:hover { + text-decoration: underline; +} + +/* Buttons */ .btn-primary { - color: #fff; - background-color: #0073e6; - border-color: #0073e6; + background-color: #0073e6; + border-color: #0073e6; + color: #fff; +} + +.btn-primary:hover { + background-color: #005bb5; + border-color: #005bb5; } .btn { - display: inline-block; - font-weight: 400; - line-height: 1.25; + padding: 0.5rem 1rem; + font-size: 1rem; + border-radius: 0.375rem; +} + +/* Responsive Enhancements */ +@media (max-width: 768px) { + .form-header { text-align: center; - white-space: nowrap; - vertical-align: middle; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - border: 1px solid transparent; - padding: .5rem 1rem; - font-size: 1rem; - border-radius: .25rem; + } + + .LanguageOptions { + width: 100%; + } + + .TopLabel { + font-size: 1.25rem; + text-align: center; + } + + .table th, + .table td { + padding: 0.75rem; + } } diff --git a/index.html b/index.html index 1f6f9d4a..b3d916dd 100644 --- a/index.html +++ b/index.html @@ -1,683 +1,153 @@ - + - - - - - - - - - - - - - - - -

.com

-
-
-
- Questions? Suggestions? Send me a PM on -
-
-
- - -
-
-

Find Great Open Source Opportunities.

-

Made for new contributors to find great Open Source projects.

-

Discover Issues and Repositories with and labels.

-
-
- -
-
-
-
- -
-
- -
-
- - - - - - - - - - - - - - - - -
- -
-
- - - - - - - + + + + + + + + + + + + + + +

+ Good First Issue.com +

+ +
+
+

+ Find Great Open Source Opportunities +

+

+ Made for new contributors to find great Open Source projects. +

+

+ Discover Issues and Repositories with: + Help Wanted + Starter Task + Good First Issue +

+ +
+
+ +
+ + +
+
+
+
+ + +
+
+
+ +
+
+
+ + + + + + + + + + + + + + + +
+
+
+
+ - - + + + + diff --git a/js/main.js b/js/main.js new file mode 100644 index 00000000..9476587c --- /dev/null +++ b/js/main.js @@ -0,0 +1,430 @@ +var searchIssuesQuery = ` +query SearchIssues($query:String!, $after: String) { + search(query:$query, type:ISSUE, first: 100, after: $after) { + edges { + node { + ... on Issue { + repository { + owner { + login + }, + updatedAt, + url, + updatedAt, + name, + stargazers { + totalCount + }, + }, + url, + createdAt, + } + } + } + + pageInfo { + endCursor + hasNextPage + } + } +} + +`; + +class RepositoryContainer { + constructor(issue) { + this.owner = issue.repository.owner.login; + this.name = issue.repository.name; + this.starcount = issue.repository.stargazers.totalCount; + this.issues = [issue]; + this.url = issue.repository.url; + this.updatedAt = issue.repository.updatedAt; + } + + addIssue(issue) { + this.issues.push(issue); + } +} + +function sortByGoodFirstIssues(repos) { + function compare(a, b) { + return returnComp( + a.issues.length, + b.issues.length, + a.starcount, + b.starcount + ); + } + + repos.sort(compare); + return repos; +} + +function returnComp(a, b, tiebreaker1, tiebreaker2) { + if (a > b) { + return -1; + } else if (b > a) { + return 1; + } else if (tiebreaker1 > tiebreaker2) { + return -1; + } else if (tiebreaker2 > tiebreaker1) { + return 1; + } + + return 0; +} + +function sortByStars(repos) { + function compare(a, b) { + return returnComp( + a.starcount, + b.starcount, + a.issues.length, + b.issues.length + ); + } + + repos.sort(compare); + return repos; +} + +function sortBy(value, repositories) { + switch (value) { + case "Good First Issue": + return sortByGoodFirstIssues(repositories); + break; + + case "Stars": + return sortByStars(repositories); + break; + } +} + +function addSlashes(string) { + var slash = '"'; + return slash + string + slash; +} + +function makeIssuesQuery(language, label, endCursor) { + return { + query: + "label:" + + addSlashes(label) + + " language:" + + language + + " " + + "state:open", + after: endCursor, + }; +} + +function getHTMLParameters() { + // get language + var langOptions = $(".LanguageOptions")[0]; + var selectedLanguage = langOptions[langOptions.selectedIndex].value; + console.log(selectedLanguage); + // get labels + + return { + language: selectedLanguage, + }; +} + +function daysBetweenDates(date1, date2) { + var oneDay = 24 * 60 * 60 * 1000; // hours*minutes*seconds*milliseconds + var diffDays = Math.round((date2 - date1) / oneDay); + return diffDays; +} + +function afterDateLimit(date, todaysDate, monthLimit) { + var dayLimit = monthLimit * 30; + var daysBtwn = daysBetweenDates(date, todaysDate); + return daysBtwn >= dayLimit; +} + +function isUndefined(object) { + return typeof object === "undefined"; +} + +function createContainers(issues, repoDict, repoNameDict) { + issues.forEach((i) => { + if (isUndefined(i)) { + return; + } + if (isUndefined(i.repository)) { + return; + } + var name = i.repository.name; + + if (isUndefined(repoNameDict[name])) { + repoNameDict[name] = name; + var container = new RepositoryContainer(i); + repoDict[name] = container; + } else { + repoDict[name].addIssue(i); + } + }); + + var arrayvalues = new Array(); + + for (var key in repoDict) { + arrayvalues.push(repoDict[key]); + } + + return arrayvalues; +} + +function createTable(issues) { + console.log("***** create table called ****"); + var repoNameDict = {}; + var repoDict = {}; + var body = document.getElementsByTagName("body")[0]; + var allRepositories = createContainers(issues, repoDict, repoNameDict); + + sortByGoodFirstIssues(allRepositories); + makeTableFromRepos(allRepositories); + return allRepositories; +} + +function makeTableFromRepos(repositories) { + makeTableElement(); + repositories.forEach((i) => { + var row = insertIntoTableElement( + i.owner, + i.name, + i.url, + i.issues.length, + i.starcount, + i.updatedAt + ); + $("table tbody")[0].appendChild(row); + }); +} + +function removeRows(table) { + $("table tbody tr").remove(); +} + +function makeTableElement() { + // create elements and a + var table = $("table")[0]; + table.classList.remove("hidden"); + removeRows(table); +} + +function appendCell(row, text) { + var cell = document.createElement("td"); + var celltext = document.createTextNode(text); + cell.appendChild(celltext); + row.appendChild(cell); +} + +function formatNameCell(repoOwnerLogin, repoName, url, row) { + var cell = document.createElement("td"); + var span = document.createElement("span"); + var a = document.createElement("a"); + var loginText = document.createTextNode(repoOwnerLogin + " / "); + var repoNameText = document.createTextNode(repoName); + + a.appendChild(loginText); + $(a).attr("href", url); + $(a).attr("target", "_blank"); + a.classList.add("ownerLoginText"); + a.appendChild(repoNameText); + + cell.appendChild(a); + row.appendChild(cell); +} + +function formatUpdatedAtDate(updatedAt) { + var date = new Date(updatedAt); + var monthNames = [ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", + ]; + + var day = date.getDate(); + var monthIndex = date.getMonth(); + var year = date.getFullYear(); + + return monthNames[monthIndex] + " " + day + " " + year; +} + +function appendStarCell(row, stars) { + var cell = document.createElement("td"); + var starSVG = starSVGIcon(); + var p1 = "

"; + var p2 = "

"; + var space = " "; + $(cell).html(p1 + starSVG + space + stars + p2); + row.appendChild(cell); +} + +function insertIntoTableElement( + repoOwnerLogin, + repoName, + url, + issueCount, + stars, + updatedAt +) { + var row = document.createElement("tr"); + formatNameCell(repoOwnerLogin, repoName, url, row); + appendCell(row, issueCount); + appendStarCell(row, stars); + appendCell(row, formatUpdatedAtDate(updatedAt)); + return row; +} + +function throwLoading(spinner) { + spinner.css("display", "block"); + console.log("throwing loading spinner"); +} + +function stopLoadingSpinner(spinner, time) { + function stopLoading() { + spinner.css("display", "none"); + } + + console.log(time); + var now = new Date().getTime(); + console.log(now); + var interval = new Date().getTime() - time; + console.log(interval); + spinner.css("display", "none"); +} + +function starSVGIcon() { + return ''; +} + +function onSubmitButton() { + var parameters = getHTMLParameters(); + if (parameters.language == "") { + return; + } + + var time = new Date().getTime(); + var spinner = $(".spinner"); + throwLoading(spinner); + var fetchCount = 0; + var labels = ["easy contribution", "good first issue", "starter-task"]; + var todaysDate = new Date(); + var issuesFetched = []; + var uniqueRepositories = []; + var labelIndex = 0; + + function setupSortMech(repos) { + $(".sortsTable").click(function () { + console.log("seen "); + var value = $(this).attr("value"); + var sortedRepos = sortBy(value, repos); + makeTableFromRepos(sortedRepos); + }); + } + + function finishedFetchingRepos() { + console.log("finished " + issuesFetched.length); + stopLoadingSpinner(spinner, new Date().getTime()); + console.log("finished " + issuesFetched.length); + var repos = createTable(issuesFetched); + setupSortMech(repos); + } + + function makeQuery(endCursor) { + var query = makeIssuesQuery( + parameters.language, + labels[labelIndex], + endCursor + ); + fetchQuery(searchIssuesQuery, query, handleData); + } + + function fetchNextPage(fetchCount, pageInfo) { + // { "query": "label:\"good first issue\" ", "after": "Y3Vyc29yOjg="} + fetchCount += 1; + makeQuery(pageInfo.endCursor); + } + + function fetchNextLabel() { + labelIndex += 1; + if (labelIndex >= labels.length) { + finishedFetchingRepos(); + } else { + makeQuery(); + } + } + + function handleData(data) { + if (data == null || data.data == null) { + fetchNextLabel(); + return; + } + + var issues = data.data.search.edges.map((edge) => { + return edge.node; + }); + console.log(issues.length); + issuesFetched = issuesFetched.concat(issues); + console.log(issuesFetched.length); + var pageInfo = data.data.search.pageInfo; + + var i = 0; + + for (i = issues.length; i >= 0; i--) { + if (isUndefined(issues[i])) { + issues.splice(i, 1); + } + } + + if (issues.length == 0) { + fetchNextLabel(); + } else if ( + pageInfo.hasNextPage && + !afterDateLimit( + new Date(issues[issues.length - 1].createdAt), + todaysDate, + 4 + ) + ) { + fetchNextPage(fetchCount, pageInfo); + } else { + fetchNextLabel(); + } + } + + makeQuery(); +} + +function fetchQuery(query, variables, callback) { + //https://graphql.org/graphql-js/graphql-clients/ + + var body = JSON.stringify({ + query, + variables: variables, + }); + + fetch("https://api.github.com/graphql", { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + Authorization: "Bearer" + " " + token, + }, + body: body, + }) + .then((r) => r.json()) + .then((data) => { + callback(data); + }); +}