From a1e2a3cfa7a9a8becc5eac87a2499d96e6858b13 Mon Sep 17 00:00:00 2001 From: Keziah Date: Fri, 4 Oct 2019 14:49:16 +0300 Subject: [PATCH 1/7] feat: contributor card --- layouts/css/page-modules/_jsfoundation.scss | 24 ++++++++ layouts/partials/footer.hbs | 12 ++++ static/js/main.js | 64 +++++++++++++++++++++ 3 files changed, 100 insertions(+) diff --git a/layouts/css/page-modules/_jsfoundation.scss b/layouts/css/page-modules/_jsfoundation.scss index 02a34906accd4..6b9eb2b1a320c 100644 --- a/layouts/css/page-modules/_jsfoundation.scss +++ b/layouts/css/page-modules/_jsfoundation.scss @@ -22,6 +22,30 @@ margin-left: auto; } +.thanking-contributor { + max-width: 300px; + display: flex; + align-items: center; + padding: .5em 1em; + margin-top: 1em; + border: 1px solid $white; + border-radius: 3px; + transition: opacity 1s ease-out; + opacity: 0; + height: 0; + overflow: hidden; + + &.active { + opacity: 1; + height: auto; + } + + img { + border-radius: 50%; + margin-right: 1em; + } +} + @media screen and (max-width: 700px) { .issue-link-container { flex-wrap: wrap; diff --git a/layouts/partials/footer.hbs b/layouts/partials/footer.hbs index 8d074ee2dd7f0..f5bab8d4d30e3 100644 --- a/layouts/partials/footer.hbs +++ b/layouts/partials/footer.hbs @@ -20,6 +20,18 @@

Node.js Project Licensing Information.

+ +
+ Avatar of a Node.js contributor + +

+ Thanks username for being a Node.js contributor. + + + +

+
+ diff --git a/static/js/main.js b/static/js/main.js index ba994763a3a15..11e241e3c53a4 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -49,6 +49,70 @@ }) })() +;(function () { + var contributorAvatar = document.getElementById('contributor-avatar') + var contributorUsername = document.getElementById('contributor-username') + var contributorCommits = document.getElementById('contributor-commits') + + if (contributorAvatar) { + var xhr = new window.XMLHttpRequest() + xhr.responseType = 'json' + + xhr.open('GET', 'https://api.github.com/repos/nodejs/node/contributors?per_page=1') + xhr.send() + xhr.onload = function () { + if (xhr.status !== 200) { + handleRequestError() + } else { + // Get Headers Links last page to generate a random contributor + var links = linkParser(xhr.getResponseHeader('Link')) + var randomPage = randomInt(links.last.page) + 1 + + // Fetch the contributor + xhr.open('GET', 'https://api.github.com/repos/nodejs/node/contributors?per_page=1&page=' + randomPage) + xhr.send() + xhr.onload = function () { + if (xhr.status !== 200) { + handleRequestError() + } else { + var contributor = xhr.response[0] + // Set new values + contributorAvatar.parentNode.classList.add('active') + contributorAvatar.src = contributor.avatar_url + contributorUsername.innerText = contributor.login + contributorUsername.href = contributor.html_url + contributorCommits.innerText = contributor.contributions + contributorCommits.innerText = contributor.contributions + ' contributions' + } + } + } + } + } + + function handleRequestError () { + contributorAvatar.parentNode.remove() + } + + function linkParser (linkHeader) { + var regex = /<([^?]+\?per_page=1&[a-z]+=([\d]+))>;[\s]*rel="([a-z]+)"/g + var array = [] + var object = {} + + while ((array = regex.exec(linkHeader)) !== null) { + object[array[3]] = { + url: array[1], + page: array[2] + } + } + + return object + } + + function randomInt (max) { + return Math.floor(Math.random() * Math.floor(parseInt(max))) + } +})() + ;(function (d, n) { 'use strict' From 5a4ab2e38c1fdd142dee919ac2336831ca2bf794 Mon Sep 17 00:00:00 2001 From: XhmikosR Date: Fri, 4 Oct 2019 14:57:18 +0300 Subject: [PATCH 2/7] More progress * return early * hide the block by default * remove function used only once * remove `height: 0` * add dns-prefetch for api.github.com --- layouts/css/page-modules/_jsfoundation.scss | 6 +- layouts/partials/footer.hbs | 2 +- layouts/partials/html-head.hbs | 1 + static/js/main.js | 72 ++++++++++----------- 4 files changed, 40 insertions(+), 41 deletions(-) diff --git a/layouts/css/page-modules/_jsfoundation.scss b/layouts/css/page-modules/_jsfoundation.scss index 6b9eb2b1a320c..c87596a5ecad0 100644 --- a/layouts/css/page-modules/_jsfoundation.scss +++ b/layouts/css/page-modules/_jsfoundation.scss @@ -23,6 +23,10 @@ } .thanking-contributor { + &.hidden { + display: none; + } + max-width: 300px; display: flex; align-items: center; @@ -32,12 +36,10 @@ border-radius: 3px; transition: opacity 1s ease-out; opacity: 0; - height: 0; overflow: hidden; &.active { opacity: 1; - height: auto; } img { diff --git a/layouts/partials/footer.hbs b/layouts/partials/footer.hbs index f5bab8d4d30e3..14c49cfc81633 100644 --- a/layouts/partials/footer.hbs +++ b/layouts/partials/footer.hbs @@ -21,7 +21,7 @@ Node.js Project Licensing Information.

-
+
diff --git a/static/js/main.js b/static/js/main.js index d3c2434b1b927..49ec69ab08a5f 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -50,46 +50,113 @@ })() ;(function () { - var thankingContributor = document.querySelector('.thanking-contributor') - var contributorAvatar = thankingContributor.querySelector('#contributor-avatar') - var contributorUsername = thankingContributor.querySelector('#contributor-username') - var contributorCommits = thankingContributor.querySelector('#contributor-commits') + var contributorCard = document.querySelector('.contributor-card') - if (!contributorAvatar) { + if (!contributorCard) { return } - var xhr = new window.XMLHttpRequest() - xhr.responseType = 'json' + var contributorAvatar = contributorCard.querySelector('#contributor-avatar') + var contributorUsername = contributorCard.querySelector('#contributor-username') + var contributorContributions = contributorCard.querySelector('#contributor-contributions') + var loadingSpinner = contributorCard.querySelector('.spinner') - xhr.open('GET', 'https://api.github.com/repos/nodejs/node/contributors?per_page=1') - xhr.send() - xhr.onload = function () { - if (xhr.status !== 200) { - return + if (window.IntersectionObserver) { + var observer = new window.IntersectionObserver(function (entries) { + entries.forEach(function (entry) { + if (entry.intersectionRatio > 0.5) { + // In viewport, fetch a random contributor + fetchRandomContributor() + + observer.unobserve(entry.target) + } + }) + }, + { threshold: 0.5 } + ) + + observer.observe(document.querySelector('footer')) + } else { + // Does not support IntersectionObserver + fetchRandomContributor() + } + + function fetchRandomContributor () { + var maxContributors + var fetchDate + var needToRefetch = false + + if (window.localStorage) { + maxContributors = window.localStorage.getItem('max_contributors') + fetchDate = parseInt(window.localStorage.getItem('fetch_date'), 10) + } + + // If fetch date is a month old (2592000000 ms === 30 days) + if (Date.now() - fetchDate >= 2592000000) { + needToRefetch = true + } + + // If localStorage and data is less than 1 month old, fetch 1 time + if (maxContributors && !needToRefetch) { + getContributor(Math.floor(Math.random() * Math.floor(parseInt(maxContributors))) + 1) + } else { + getMaxContributors(function (randomPage, lastPage) { + getContributor(randomPage) + + if (window.localStorage) { + window.localStorage.setItem('max_contributors', lastPage) + } + }) + } + } + + function getMaxContributors (callback) { + var xhr = new window.XMLHttpRequest() + xhr.responseType = 'json' + + xhr.open('GET', 'https://api.github.com/repos/nodejs/node/contributors?per_page=1') + xhr.send() + xhr.onload = function () { + if (xhr.status !== 200) { + return contributorCard.remove() + } + + // Get Headers Links last page to generate a random contributor + var links = linkParser(xhr.getResponseHeader('Link')) + var randomPage = Math.floor(Math.random() * Math.floor(parseInt(links.last.page, 10))) + 1 + + if (window.localStorage) { + window.localStorage.setItem('fetch_date', Date.now()) + } + + callback(randomPage, links.last.page) } + } - // Get Headers Links last page to generate a random contributor - var links = linkParser(xhr.getResponseHeader('Link')) - var randomPage = Math.floor(Math.random() * Math.floor(parseInt(links.last.page))) + 1 + function getContributor (randomPage) { + var xhr = new window.XMLHttpRequest() + xhr.responseType = 'json' - // Fetch the contributor xhr.open('GET', 'https://api.github.com/repos/nodejs/node/contributors?per_page=1&page=' + randomPage) xhr.send() xhr.onload = function () { if (xhr.status !== 200) { - return + return contributorCard.remove() } var contributor = xhr.response[0] + + // Remove loading spinner and show avatar + loadingSpinner.remove() + contributorAvatar.classList.remove('hidden') // Set new values - thankingContributor.classList.remove('hidden') - contributorAvatar.parentNode.classList.add('active') contributorAvatar.src = contributor.avatar_url + contributorAvatar.parentElement.href = contributor.html_url contributorUsername.innerText = contributor.login contributorUsername.href = contributor.html_url - contributorCommits.innerText = contributor.contributions - contributorCommits.innerText = contributor.contributions + ' contributions' + contributorContributions.innerText = contributor.contributions + contributorContributions.innerText = contributor.contributions + ' contributions' + contributorContributions.parentElement.href = 'https://github.com/nodejs/node/commits?author=' + contributor.login } } From d70fee63ab9796258ae174576faec2b7e03d56fb Mon Sep 17 00:00:00 2001 From: XhmikosR Date: Tue, 26 Nov 2019 14:56:41 +0200 Subject: [PATCH 4/7] Use addEventListener and remove redundant assignment --- static/js/main.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/static/js/main.js b/static/js/main.js index 49ec69ab08a5f..dd9bb8ec572d3 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -116,7 +116,7 @@ xhr.open('GET', 'https://api.github.com/repos/nodejs/node/contributors?per_page=1') xhr.send() - xhr.onload = function () { + xhr.addEventListener('load', function () { if (xhr.status !== 200) { return contributorCard.remove() } @@ -130,7 +130,7 @@ } callback(randomPage, links.last.page) - } + }) } function getContributor (randomPage) { @@ -139,7 +139,7 @@ xhr.open('GET', 'https://api.github.com/repos/nodejs/node/contributors?per_page=1&page=' + randomPage) xhr.send() - xhr.onload = function () { + xhr.addEventListener('load', function () { if (xhr.status !== 200) { return contributorCard.remove() } @@ -154,10 +154,9 @@ contributorAvatar.parentElement.href = contributor.html_url contributorUsername.innerText = contributor.login contributorUsername.href = contributor.html_url - contributorContributions.innerText = contributor.contributions contributorContributions.innerText = contributor.contributions + ' contributions' contributorContributions.parentElement.href = 'https://github.com/nodejs/node/commits?author=' + contributor.login - } + }) } function linkParser (linkHeader) { From aa06d21d5d01a20c54d7c8c0c0887cee90d704ae Mon Sep 17 00:00:00 2001 From: KeziahMoselle Date: Sat, 7 Dec 2019 19:27:59 +0200 Subject: [PATCH 5/7] fix: rel nofollow, textcontent, image parameter, text wrapping --- layouts/css/page-modules/_contributor-card.scss | 16 +++++++++++++--- layouts/partials/contributor-card.hbs | 4 ++-- static/js/main.js | 6 +++--- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/layouts/css/page-modules/_contributor-card.scss b/layouts/css/page-modules/_contributor-card.scss index 63e2d4bf99475..190b1372d083e 100644 --- a/layouts/css/page-modules/_contributor-card.scss +++ b/layouts/css/page-modules/_contributor-card.scss @@ -1,5 +1,6 @@ .contributor-card { - height: 42px; + display: flex; + min-height: 42px; width: 300px; padding: 1.5em 1em; margin: 1em auto; @@ -7,7 +8,6 @@ border-radius: 3px; > a { - float: left; height: 40px; width: 40px; margin-right: 1em; @@ -17,8 +17,12 @@ margin: 0; } + img { + max-width: initial; + } + .spinner { - margin: 5px 0; + margin: 5px; width: 30px; height: 30px; border-top-color: $white; @@ -43,3 +47,9 @@ transform: rotate(360deg); } } + +@media (max-width: 350px) { + .contributor-card { + width: auto; + } +} diff --git a/layouts/partials/contributor-card.hbs b/layouts/partials/contributor-card.hbs index 09a8794b78a7a..6f189eb3e2c1a 100644 --- a/layouts/partials/contributor-card.hbs +++ b/layouts/partials/contributor-card.hbs @@ -1,11 +1,11 @@
- +

- Thank you username for being a Node.js contributor. + Thank you username for being a Node.js contributor 0 contributions diff --git a/static/js/main.js b/static/js/main.js index dd9bb8ec572d3..4d2744878a09e 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -150,11 +150,11 @@ loadingSpinner.remove() contributorAvatar.classList.remove('hidden') // Set new values - contributorAvatar.src = contributor.avatar_url + contributorAvatar.src = contributor.avatar_url + '&s=80' contributorAvatar.parentElement.href = contributor.html_url - contributorUsername.innerText = contributor.login + contributorUsername.textContent = contributor.login contributorUsername.href = contributor.html_url - contributorContributions.innerText = contributor.contributions + ' contributions' + contributorContributions.textContent = contributor.contributions + ' contributions' contributorContributions.parentElement.href = 'https://github.com/nodejs/node/commits?author=' + contributor.login }) } From 3edc27366091ce13771c205e593fd2bb36381957 Mon Sep 17 00:00:00 2001 From: XhmikosR Date: Sun, 8 Dec 2019 13:39:57 +0200 Subject: [PATCH 6/7] More tweaks and fix IE >= 10 JS. Also, move `.hidden` to utils. --- layouts/css/_utils.scss | 4 + .../css/page-modules/_contributor-card.scss | 28 +------ layouts/css/page-modules/_header.scss | 4 - layouts/css/page-modules/_jsfoundation.scss | 4 - layouts/css/page-modules/spinner.scss | 18 +++++ layouts/css/styles.scss | 1 + layouts/partials/contributor-card.hbs | 4 +- static/js/main.js | 79 ++++++++++--------- 8 files changed, 71 insertions(+), 71 deletions(-) create mode 100644 layouts/css/page-modules/spinner.scss diff --git a/layouts/css/_utils.scss b/layouts/css/_utils.scss index 47bb272352fe8..92b230207c0d9 100644 --- a/layouts/css/_utils.scss +++ b/layouts/css/_utils.scss @@ -25,3 +25,7 @@ white-space: nowrap; border: none; } + +.hidden { + display: none; +} diff --git a/layouts/css/page-modules/_contributor-card.scss b/layouts/css/page-modules/_contributor-card.scss index 190b1372d083e..7d10711d4187c 100644 --- a/layouts/css/page-modules/_contributor-card.scss +++ b/layouts/css/page-modules/_contributor-card.scss @@ -1,5 +1,7 @@ .contributor-card { display: flex; + justify-content: space-between; + align-items: center; min-height: 42px; width: 300px; padding: 1.5em 1em; @@ -18,33 +20,11 @@ } img { - max-width: initial; + max-width: none; } - .spinner { + .spinner-border { margin: 5px; - width: 30px; - height: 30px; - border-top-color: $white; - border-left-color: #444; - animation: spinner 400ms linear infinite; - border-bottom-color: transparent; - border-right-color: transparent; - border-style: solid; - border-width: 2px; - border-radius: 50%; - box-sizing: border-box; - display: inline-block; - } - - .hidden { - display: none; - } -} - -@keyframes spinner { - 100% { - transform: rotate(360deg); } } diff --git a/layouts/css/page-modules/_header.scss b/layouts/css/page-modules/_header.scss index 955a41604d85e..1f173bc48a22d 100644 --- a/layouts/css/page-modules/_header.scss +++ b/layouts/css/page-modules/_header.scss @@ -52,10 +52,6 @@ header { margin: 0; padding: 0; - &.hidden { - display: none; - } - a { color: $light-gray2; } diff --git a/layouts/css/page-modules/_jsfoundation.scss b/layouts/css/page-modules/_jsfoundation.scss index 0fc728a2ffaf5..588c3efb4115e 100644 --- a/layouts/css/page-modules/_jsfoundation.scss +++ b/layouts/css/page-modules/_jsfoundation.scss @@ -23,10 +23,6 @@ } .thanking-contributor { - &.hidden { - display: none; - } - max-width: 300px; display: flex; align-items: center; diff --git a/layouts/css/page-modules/spinner.scss b/layouts/css/page-modules/spinner.scss new file mode 100644 index 0000000000000..b3756d56dc702 --- /dev/null +++ b/layouts/css/page-modules/spinner.scss @@ -0,0 +1,18 @@ +// borrowed from Bootstrap +@keyframes spinner-border { + to { + transform: rotate(360deg); + } +} + +.spinner-border { + box-sizing: border-box; + display: inline-block; + width: 2rem; + height: 2rem; + vertical-align: text-bottom; + border: .25em solid currentColor; + border-right-color: transparent; + border-radius: 50%; + animation: spinner-border .75s linear infinite; +} diff --git a/layouts/css/styles.scss b/layouts/css/styles.scss index aff7d30b8c800..971838040c6ee 100644 --- a/layouts/css/styles.scss +++ b/layouts/css/styles.scss @@ -22,6 +22,7 @@ @import "page-modules/release-schedule"; @import "page-modules/resources"; @import "page-modules/contributor-card"; +@import "page-modules/spinner"; @import "vendor/prism-tomorrow"; article a { diff --git a/layouts/partials/contributor-card.hbs b/layouts/partials/contributor-card.hbs index 6f189eb3e2c1a..ea68ef996eb92 100644 --- a/layouts/partials/contributor-card.hbs +++ b/layouts/partials/contributor-card.hbs @@ -1,6 +1,8 @@

- +
+ Loading... +
diff --git a/static/js/main.js b/static/js/main.js index 4d2744878a09e..b66d44b671d02 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -59,7 +59,7 @@ var contributorAvatar = contributorCard.querySelector('#contributor-avatar') var contributorUsername = contributorCard.querySelector('#contributor-username') var contributorContributions = contributorCard.querySelector('#contributor-contributions') - var loadingSpinner = contributorCard.querySelector('.spinner') + var loadingSpinner = contributorCard.querySelector('.spinner-border') if (window.IntersectionObserver) { var observer = new window.IntersectionObserver(function (entries) { @@ -112,51 +112,54 @@ function getMaxContributors (callback) { var xhr = new window.XMLHttpRequest() - xhr.responseType = 'json' - - xhr.open('GET', 'https://api.github.com/repos/nodejs/node/contributors?per_page=1') - xhr.send() - xhr.addEventListener('load', function () { - if (xhr.status !== 200) { - return contributorCard.remove() - } - - // Get Headers Links last page to generate a random contributor - var links = linkParser(xhr.getResponseHeader('Link')) - var randomPage = Math.floor(Math.random() * Math.floor(parseInt(links.last.page, 10))) + 1 - - if (window.localStorage) { - window.localStorage.setItem('fetch_date', Date.now()) + xhr.open('GET', 'https://api.github.com/repos/nodejs/node/contributors?per_page=1', true) + + xhr.onreadystatechange = function () { + if (xhr.readyState === 4) { + if (xhr.status === 200) { + // Get Headers Links last page to generate a random contributor + var links = linkParser(xhr.getResponseHeader('Link')) + var randomPage = Math.floor(Math.random() * Math.floor(parseInt(links.last.page, 10))) + 1 + + if (window.localStorage) { + window.localStorage.setItem('fetch_date', Date.now()) + } + callback(randomPage, links.last.page) + } else { + return contributorCard.parentNode.removeChild(contributorCard) + } } + } - callback(randomPage, links.last.page) - }) + xhr.send() } function getContributor (randomPage) { var xhr = new window.XMLHttpRequest() - xhr.responseType = 'json' - - xhr.open('GET', 'https://api.github.com/repos/nodejs/node/contributors?per_page=1&page=' + randomPage) - xhr.send() - xhr.addEventListener('load', function () { - if (xhr.status !== 200) { - return contributorCard.remove() + xhr.open('GET', 'https://api.github.com/repos/nodejs/node/contributors?per_page=1&page=' + randomPage, true) + + xhr.onreadystatechange = function () { + if (xhr.readyState === 4) { + if (xhr.status === 200) { + var contributor = JSON.parse(xhr.responseText)[0] + + // Remove loading spinner and show avatar + loadingSpinner.parentNode.removeChild(loadingSpinner) + contributorAvatar.classList.remove('hidden') + // Set new values + contributorAvatar.src = contributor.avatar_url + '&s=80' + contributorAvatar.parentElement.href = contributor.html_url + contributorUsername.textContent = contributor.login + contributorUsername.href = contributor.html_url + contributorContributions.textContent = contributor.contributions + ' contributions' + contributorContributions.parentElement.href = 'https://github.com/nodejs/node/commits?author=' + contributor.login + } else { + return contributorCard.parentNode.removeChild(contributorCard) + } } + } - var contributor = xhr.response[0] - - // Remove loading spinner and show avatar - loadingSpinner.remove() - contributorAvatar.classList.remove('hidden') - // Set new values - contributorAvatar.src = contributor.avatar_url + '&s=80' - contributorAvatar.parentElement.href = contributor.html_url - contributorUsername.textContent = contributor.login - contributorUsername.href = contributor.html_url - contributorContributions.textContent = contributor.contributions + ' contributions' - contributorContributions.parentElement.href = 'https://github.com/nodejs/node/commits?author=' + contributor.login - }) + xhr.send() } function linkParser (linkHeader) { From edbae363ecafbfce83e042ea5d9d651014794939 Mon Sep 17 00:00:00 2001 From: Martijn Cuppens Date: Thu, 16 Jan 2020 14:03:05 +0100 Subject: [PATCH 7/7] IE fixes --- layouts/css/page-modules/_contributor-card.scss | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/layouts/css/page-modules/_contributor-card.scss b/layouts/css/page-modules/_contributor-card.scss index 7d10711d4187c..699608986bed2 100644 --- a/layouts/css/page-modules/_contributor-card.scss +++ b/layouts/css/page-modules/_contributor-card.scss @@ -1,6 +1,5 @@ .contributor-card { display: flex; - justify-content: space-between; align-items: center; min-height: 42px; width: 300px; @@ -12,15 +11,13 @@ > a { height: 40px; width: 40px; - margin-right: 1em; + flex: 0 0 auto; } p { + padding-left: 1em; margin: 0; - } - - img { - max-width: none; + flex: 1 1 1px; } .spinner-border {