diff --git a/gatsby-config.js b/gatsby-config.js
index 65715d9f..2096bb54 100644
--- a/gatsby-config.js
+++ b/gatsby-config.js
@@ -17,6 +17,10 @@ module.exports = {
{ key: 'guides', name: '📚 Guias' },
{ key: 'archive', name: '📥 Arquivo' },
],
+ github: {
+ owner: "leic-pt",
+ repository: "resumos-leic",
+ },
navbar: {
siteTitle: 'Resumos LEIC-A',
links: [
@@ -49,6 +53,7 @@ module.exports = {
path: `${__dirname}/content`,
},
},
+ `gatsby-source-github-contributors`,
`gatsby-plugin-image`,
`gatsby-plugin-sharp`,
{
diff --git a/package.json b/package.json
index 04051966..22a81854 100755
--- a/package.json
+++ b/package.json
@@ -20,6 +20,7 @@
},
"license": "GPL-3.0",
"devDependencies": {
+ "@octokit/graphql": "^7.0.2",
"prettier": "^3.0.0"
},
"dependencies": {
diff --git a/plugins/gatsby-source-github-contributors/gatsby-node.js b/plugins/gatsby-source-github-contributors/gatsby-node.js
new file mode 100644
index 00000000..0bacb410
--- /dev/null
+++ b/plugins/gatsby-source-github-contributors/gatsby-node.js
@@ -0,0 +1,101 @@
+const { graphql } = require("@octokit/graphql");
+
+// mutates `contributors`
+function mergeContributors(contributors, pullRequests) {
+ pullRequests.filter(({author}) => !!author.login).forEach(({author, labels}) => {
+ const { name, login } = author;
+ const contributor = contributors[login] || (contributors[login] = {});
+ const labelSet = contributor.labels || (contributor.labels = new Set());
+
+ labels.nodes.filter(label => !!label.name).forEach(label => labelSet.add(label.name));
+
+ if (!contributor.name) {
+ contributor.name = name;
+ }
+ })
+}
+
+exports.sourceNodes = async ({
+ actions,
+ cache,
+ createContentDigest,
+ createNodeId,
+ getNodesByType,
+}) => {
+ const { createNode, touchNode } = actions
+
+ const githubToken = process.env.GITHUB_TOKEN;
+ if (!githubToken) {
+ console.info("No GitHub token found (GITHUB_TOKEN env var), skipping contributors list");
+ return;
+ }
+
+ const graphqlGh = graphql.defaults({
+ headers: {
+ authorization: `bearer ${githubToken}`,
+ }
+ });
+
+ const contributors = {};
+
+ // TODO cache old pagination
+ // const lastUpdated = await cache.get("lastUpdated")
+ // // 30 days cache
+ // if (lastUpdated > Date.now() - 1000 * 60 * 60 * 24 * 30) {
+ // getNodesByType("Space").forEach(node => touchNode(node))
+ // return
+ // }
+
+ // await cache.set("lastUpdated", Date.now())
+
+ const getPullRequests = async (cursor) => {
+ const { repository } = await graphqlGh(`
+ query fetchPullRequests($cursor: String) {
+ repository(owner: "leic-pt", name: "resumos-leic") {
+ pullRequests(first: 10, after: $cursor, states: [MERGED], orderBy: {field: UPDATED_AT, direction: DESC}) {
+ pageInfo {
+ endCursor
+ hasNextPage
+ }
+ nodes {
+ updatedAt
+ labels (first: 50) {
+ nodes {
+ name
+ }
+ }
+ author {
+ ... on User {
+ name
+ login
+ }
+ }
+ }
+ }
+ }
+ }`);
+
+ return repository?.pullRequests
+ }
+
+ mergeContributors(contributors, (await getPullRequests(null)).nodes);
+
+ Object.entries(contributors).forEach(([username, contributor]) => {
+ const data = {
+ username,
+ ...contributor,
+ name: contributor.name || username,
+ labels: [...(contributor.labels || [])],
+ };
+
+ createNode({
+ ...data,
+ id: createNodeId(data.username),
+ internal: {
+ type: "Contributor",
+ content: JSON.stringify(data),
+ contentDigest: createContentDigest(data),
+ },
+ })
+ });
+}
diff --git a/plugins/gatsby-source-github-contributors/package.json b/plugins/gatsby-source-github-contributors/package.json
new file mode 100644
index 00000000..00dbedb3
--- /dev/null
+++ b/plugins/gatsby-source-github-contributors/package.json
@@ -0,0 +1,4 @@
+{
+ "name": "gatsby-source-github-contributors",
+ "description": "Fetch GitHub contributors for this repository"
+}
diff --git a/src/components/Contributors.js b/src/components/Contributors.js
new file mode 100644
index 00000000..e5af2028
--- /dev/null
+++ b/src/components/Contributors.js
@@ -0,0 +1,60 @@
+import { Link, useStaticQuery, graphql } from 'gatsby';
+import React, { useMemo } from 'react';
+import ExternalLink from './ExternalLink';
+import SearchBar from './SearchBar';
+import ThemeSettings from './ThemeSettings';
+
+const Contributors = ({ getLabelName, contributors }) => {
+ const data = useStaticQuery(graphql`
+ query ContributorsQuery {
+ site {
+ siteMetadata {
+ github {
+ owner
+ repository
+ }
+ }
+ }
+ }
+ `);
+
+ const {owner, repository} = data.site.siteMetadata.github;
+
+ const sortedContributors = useMemo(() => {
+ return contributors.map(contributor => {
+ const labels = contributor.labels
+ .map(getLabelName)
+ .filter(Boolean)
+ .map(label => ({ label, bold: false }));
+ return {
+ username: contributor.username,
+ name: contributor.name,
+ labels,
+ boldCount: labels.filter(({bold}) => bold).length,
+ }
+ })
+ .sort((a,b) => a.name.localeCompare(b.name))
+ .sort((a,b) => b.boldCount - a.boldCount)
+ .sort((a,b) => b.labels.length - a.labels.length)
+ }, [contributors, getLabelName])
+
+ return (
+
+ {sortedContributors.map(contributor => (
+ -
+
+ {contributor.name}
+ {' ('}{contributor.labels.map(({label, bold}, index) => (
+ <>
+ {index > 0 && ', '}
+ {bold ? ({label}) : label}
+ >
+ ))})
+
+ ))}
+
+ );
+};
+
+export default Contributors;
diff --git a/src/components/HomePageLayout.js b/src/components/HomePageLayout.js
index bf6402e5..ff048217 100644
--- a/src/components/HomePageLayout.js
+++ b/src/components/HomePageLayout.js
@@ -1,5 +1,5 @@
import { graphql } from 'gatsby';
-import React from 'react';
+import React, { useCallback } from 'react';
import IstLogo from '../images/ist-logo.svg';
import '../styles/homepage.css';
import '../styles/main.css';
@@ -8,9 +8,25 @@ import Footer from './Footer';
import Navbar from './Navbar';
import PageMetadata from './PageMetadata';
import SectionButton, { SectionButtonLayout } from './SectionButton';
+import Contributors from './Contributors';
const HomePageLayout = ({ data }) => {
- const { markdownRemark: page } = data;
+ const { markdownRemark: page, allContributor: contributors } = data;
+
+ const getLabelName = useCallback((label) => {
+ const { years } = page.frontmatter;
+
+ for (const year of years) {
+ for (const semester of year.semesters) {
+ for (const course of semester.courses) {
+ if (course.link === `/${label}`) {
+ return course.name;
+ }
+ }
+ }
+ }
+ }, [page.frontmatter.years]);
+
return (
@@ -47,7 +63,10 @@ const HomePageLayout = ({ data }) => {
))}
-
+
);
@@ -80,6 +99,13 @@ export const pageQuery = graphql`
}
}
}
+ allContributor {
+ nodes {
+ username
+ name
+ labels
+ }
+ }
}
`;
diff --git a/yarn.lock b/yarn.lock
index 8ee56804..7f9367a7 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1860,6 +1860,54 @@
"@nodelib/fs.scandir" "2.1.5"
fastq "^1.6.0"
+"@octokit/endpoint@^9.0.0":
+ version "9.0.4"
+ resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-9.0.4.tgz#8afda5ad1ffc3073d08f2b450964c610b821d1ea"
+ integrity sha512-DWPLtr1Kz3tv8L0UvXTDP1fNwM0S+z6EJpRcvH66orY6Eld4XBMCSYsaWp4xIm61jTWxK68BrR7ibO+vSDnZqw==
+ dependencies:
+ "@octokit/types" "^12.0.0"
+ universal-user-agent "^6.0.0"
+
+"@octokit/graphql@^7.0.2":
+ version "7.0.2"
+ resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-7.0.2.tgz#3df14b9968192f9060d94ed9e3aa9780a76e7f99"
+ integrity sha512-OJ2iGMtj5Tg3s6RaXH22cJcxXRi7Y3EBqbHTBRq+PQAqfaS8f/236fUrWhfSn8P4jovyzqucxme7/vWSSZBX2Q==
+ dependencies:
+ "@octokit/request" "^8.0.1"
+ "@octokit/types" "^12.0.0"
+ universal-user-agent "^6.0.0"
+
+"@octokit/openapi-types@^19.1.0":
+ version "19.1.0"
+ resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-19.1.0.tgz#75ec7e64743870fc73e1ab4bc6ec252ecdd624dc"
+ integrity sha512-6G+ywGClliGQwRsjvqVYpklIfa7oRPA0vyhPQG/1Feh+B+wU0vGH1JiJ5T25d3g1JZYBHzR2qefLi9x8Gt+cpw==
+
+"@octokit/request-error@^5.0.0":
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-5.0.1.tgz#277e3ce3b540b41525e07ba24c5ef5e868a72db9"
+ integrity sha512-X7pnyTMV7MgtGmiXBwmO6M5kIPrntOXdyKZLigNfQWSEQzVxR4a4vo49vJjTWX70mPndj8KhfT4Dx+2Ng3vnBQ==
+ dependencies:
+ "@octokit/types" "^12.0.0"
+ deprecation "^2.0.0"
+ once "^1.4.0"
+
+"@octokit/request@^8.0.1":
+ version "8.1.6"
+ resolved "https://registry.yarnpkg.com/@octokit/request/-/request-8.1.6.tgz#a76a859c30421737a3918b40973c2ff369009571"
+ integrity sha512-YhPaGml3ncZC1NfXpP3WZ7iliL1ap6tLkAp6MvbK2fTTPytzVUyUesBBogcdMm86uRYO5rHaM1xIWxigWZ17MQ==
+ dependencies:
+ "@octokit/endpoint" "^9.0.0"
+ "@octokit/request-error" "^5.0.0"
+ "@octokit/types" "^12.0.0"
+ universal-user-agent "^6.0.0"
+
+"@octokit/types@^12.0.0":
+ version "12.4.0"
+ resolved "https://registry.yarnpkg.com/@octokit/types/-/types-12.4.0.tgz#8f97b601e91ce6b9776ed8152217e77a71be7aac"
+ integrity sha512-FLWs/AvZllw/AGVs+nJ+ELCDZZJk+kY0zMen118xhL2zD0s1etIUHm1odgjP7epxYU1ln7SZxEUWYop5bhsdgQ==
+ dependencies:
+ "@octokit/openapi-types" "^19.1.0"
+
"@parcel/bundler-default@2.8.3":
version "2.8.3"
resolved "https://registry.yarnpkg.com/@parcel/bundler-default/-/bundler-default-2.8.3.tgz#d64739dbc2dbd59d6629861bf77a8083aced5229"
@@ -4775,6 +4823,11 @@ dependency-graph@^0.11.0:
resolved "https://registry.yarnpkg.com/dependency-graph/-/dependency-graph-0.11.0.tgz#ac0ce7ed68a54da22165a85e97a01d53f5eb2e27"
integrity sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==
+deprecation@^2.0.0:
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919"
+ integrity sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==
+
dequal@^2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be"
@@ -11866,6 +11919,11 @@ unist-util-visit@^4.0.0:
unist-util-is "^5.0.0"
unist-util-visit-parents "^5.1.1"
+universal-user-agent@^6.0.0:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-6.0.1.tgz#15f20f55da3c930c57bddbf1734c6654d5fd35aa"
+ integrity sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==
+
universalify@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d"