Skip to content

Commit e4e333e

Browse files
authored
Merge pull request #634 from marcpicaud/github-stars
⭐️GitHub stargazers count
2 parents f6d9543 + 2e880d1 commit e4e333e

File tree

10 files changed

+149
-69
lines changed

10 files changed

+149
-69
lines changed

.env.example

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Generate your GitHub Personnal Access Token here: https://github.com/settings/tokens
2+
GITHUB_PERSONAL_ACCESS_TOKEN=put_your_github_token_here

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
.DS_Store
2+
.env
23
.idea
34
.build*
45
_site

README.md

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,13 @@ We ask that you make pull requests because changes to this repository will get d
1212

1313
## Running the entire app locally
1414

15-
This is a static site with a build step, simply run `npm start`:
15+
This is a static site with a build step. The build step uses a [GitHub Personnal Access Token](https://github.com/settings/tokens) that you need to generate and put in a .env file. **Actually you don't need to configure any authorization on this access token**, it just serves the purpose of unleashing the GitHub API Rate Limit.
16+
17+
```
18+
cp .env.example .env
19+
// then put your GitHub Personnal Access Token in the .env file
20+
```
21+
After that, simply run `npm start`:
1622

1723
```
1824
npm install
@@ -25,10 +31,10 @@ Please refer to the [nodeschool site](http://nodeschool.io/#workshoppers) for de
2531

2632
- **Globally** (easiest)
2733

28-
Depending on the npm version, `npm packages` get installed in different routes. To have access to them globally. Do
29-
34+
Depending on the npm version, `npm packages` get installed in different routes. To have access to them globally. Do
35+
3036
`npm install -global package_name` or `npm install -g package_name`
31-
37+
3238
If you get a `permission denied` **error**. Run the previous command with `sudo`.
3339

3440
`sudo npm install -g package_name`
@@ -52,10 +58,10 @@ If you would like to keep all the node_school workshop packages inside a custom
5258
```
5359
~ mkdir -p node_school
5460
cd node_school
55-
npm install javascripting
61+
npm install javascripting
5662
```
5763

58-
From within the `node_school` directory now run `node_modules/learnyounode/bin/javascripting` to start it.
64+
From within the `node_school` directory now run `node_modules/learnyounode/bin/javascripting` to start it.
5965

6066
This is because you need to run the executable from within the directory itself since it's not available globally in your `$PATH`
6167

index.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ <h4><a class="js-workshop-link" href="https://www.github.com/jlord/git-it-electr
201201
<p>Download the <a href="https://github.com/jlord/git-it-electron/releases" target="_blank" rel="noreferrer noopener">latest desktop app release</a>.</p>
202202
</div>
203203
<div id="elementary-electron" class="workshopper">
204-
<h4><a href="https://www.github.com/maxogden/elementary-electron" target="_blank" rel="noreferrer noopener">Elementary Electron</a></h4>
204+
<h4><a class="js-workshop-link" href="https://www.github.com/maxogden/elementary-electron" target="_blank" rel="noreferrer noopener">Elementary Electron</a></h4>
205205
<p data-i18n="workshopper-elementary-electron">Make a desktop application using Node and Chromium with Electron</p>
206206
<code>npm install -g elementary-electron</code>
207207
</div>
@@ -293,7 +293,7 @@ <h4><a class="js-workshop-link" href="https://github.com/stackgl/webgl-workshop"
293293
<code>npm install -g webgl-workshop</code>
294294
</div>
295295
<div id="esnext-generation" class="workshopper">
296-
<h4><a href="https://github.com/jesstelford/esnext-generation" target="_blank" rel="noreferrer noopener">ESNext Generation</a></h4>
296+
<h4><a class="js-workshop-link" href="https://github.com/jesstelford/esnext-generation" target="_blank" rel="noreferrer noopener">ESNext Generation</a></h4>
297297
<p data-i18n="workshopper-esnext-generation">Intro to ES6 Iterators, their use, and how they relate to Generators.</p>
298298
<code>npm install -g esnext-generation</code>
299299
</div>
@@ -416,7 +416,7 @@ <h4><a class="js-workshop-link" href="https://github.com/bevacqua/perfschool" ta
416416
<code>npm install -g perfschool</code>
417417
</div>
418418
<div id="web-audio-school" class="workshopper">
419-
<h4><a href="https://github.com/mmckegg/web-audio-school" target="_blank" rel="noreferrer noopener">Web Audio School</a></h4>
419+
<h4><a class="js-workshop-link" href="https://github.com/mmckegg/web-audio-school" target="_blank" rel="noreferrer noopener">Web Audio School</a></h4>
420420
<p data-i18n="workshopper-web-audio-school">Learn the Web Audio API by completing a series of interactive lessons with a focus on music.</p>
421421
<code>npm install -g web-audio-school</code>
422422
</div>
@@ -451,7 +451,7 @@ <h4><a class="js-workshop-link" href="https://github.com/excellalabs/js-best-pra
451451
<code>npm install -g js-best-practices</code>
452452
</div>
453453
<div id="scope-chains-closures" class="workshopper">
454-
<h4><a href="https://www.github.com/workshopper/scope-chains-closures" target="_blank" rel="noreferrer noopener">Scope Chains & Closures</a></h4>
454+
<h4><a class="js-workshop-link" href="https://www.github.com/workshopper/scope-chains-closures" target="_blank" rel="noreferrer noopener">Scope Chains & Closures</a></h4>
455455
<p data-i18n="workshopper-scope-chains-closures">Learn the details of Scope, Scope Chains, Closures, and Garbage Collection.</p>
456456
<code>npm i @workshoppers/scope-chains-closures -g</code><br>
457457
<code>scope-chains-closures</code>

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,10 @@
3434
"devDependencies": {
3535
"after-all": "^2.0.1",
3636
"autoprefixer-stylus": "^0.9.4",
37+
"axios": "^0.18.0",
3738
"browserify": "^12.0.1",
3839
"cheerio": "^0.19.0",
40+
"dotenv": "^6.1.0",
3941
"gaze": "^0.5.1",
4042
"glob": "^6.0.3",
4143
"jsdom": "^7.1.0",

scripts/build-copy.js

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,21 @@ function process(file) {
1515
var output = Path.join('.build', file)
1616
mkdirp.sync(Path.dirname(output))
1717
Fs.createReadStream(file)
18-
.pipe(
19-
Fs.createWriteStream(output)
20-
.on('end', function () {
21-
delete processing[file]
22-
if (waiting[file]) {
23-
delete waiting[file]
24-
process(file)
25-
}
26-
})
27-
)
18+
.pipe(
19+
Fs.createWriteStream(output)
20+
.on('end', function () {
21+
delete processing[file]
22+
if (waiting[file]) {
23+
delete waiting[file]
24+
process(file)
25+
}
26+
})
27+
)
2828
}
2929

3030
cmdwatcher('build-copy'
31-
, ['!(node_modules)/**/*.@(png|jpg|svg|css|gif|ico)','*.@(png|jpg|svg|css|gif|ico)', 'js/*', 'CNAME']
32-
, function processFiles(files)
31+
, ['!(node_modules)/**/*.@(png|jpg|svg|css|gif|ico)','*.@(png|jpg|svg|css|gif|ico|map)', 'js/*', 'CNAME']
32+
, function processFiles(files)
3333
{
3434
files.forEach(process)
3535
})

scripts/build-html.js

Lines changed: 111 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -4,60 +4,75 @@ const Path = require('path')
44
const Fs = require('fs')
55
const jsdom = require('jsdom')
66
const mkdirp = require('mkdirp')
7+
const { performance } = require('perf_hooks');
78
const cmdwatcher = require('./util/cmdwatcher')
89
const LANGS = Path.join(__dirname, '../languages/languages.json')
910
const FOOTER = Path.join(__dirname, '../footer.html')
1011

12+
const env = require('dotenv').config();
13+
if (env.error) {
14+
throw env.error;
15+
}
16+
17+
if (!process.env.GITHUB_PERSONAL_ACCESS_TOKEN) {
18+
throw new Error('GITHUB_PERSONAL_ACCESS_TOKEN environment variable is not found');
19+
}
20+
21+
const axios = require('axios').create({
22+
baseURL: 'https://api.github.com/',
23+
headers: { 'Authorization': `token ${process.env.GITHUB_PERSONAL_ACCESS_TOKEN}` }
24+
});
25+
1126
function createLangButton(dom, file, lang, langName) {
1227
var li = dom.createElement('li')
13-
li.className = 'nav-lang-' + lang
14-
li.innerHTML = '<a href="/' + (lang === 'en' ? '' : lang + '/') + (file === 'index.html' ? '' : file) + '" class="switch-lang" lang="' + lang + '">' + langName + '</a>'
15-
return li
28+
li.className = 'nav-lang-' + lang
29+
li.innerHTML = '<a href="/' + (lang === 'en' ? '' : lang + '/') + (file === 'index.html' ? '' : file) + '" class="switch-lang" lang="' + lang + '">' + langName + '</a>'
30+
return li
1631
}
1732

1833
function createSkipButton(dom) {
19-
var a = dom.createElement('a')
20-
a.className = 'skip'
21-
a.href = '#main'
22-
a.innerHTML = 'Skip to Content'
23-
return a
34+
var a = dom.createElement('a')
35+
a.className = 'skip'
36+
a.href = '#main'
37+
a.innerHTML = 'Skip to Content'
38+
return a
2439
}
2540

2641
function addTranslationNav(dom, file, languages) {
2742
var items = []
28-
var nav = dom.createElement('ul')
29-
nav.className = "nav-lang"
30-
31-
Object.keys(languages).sort(function (a, b) {
32-
return languages[a] > languages[b] ? 1 : -1
33-
}).forEach(function(lang) {
34-
var button = createLangButton(dom, file, lang, languages[lang])
35-
items.push(button.firstChild)
36-
nav.appendChild(button)
37-
})
38-
var node = dom.querySelector('header > *:first-child')
39-
if (node) {
40-
node.parentNode.insertBefore(createSkipButton(dom), node)
41-
node.parentNode.insertBefore(nav, node)
42-
}
43-
return items
43+
var nav = dom.createElement('ul')
44+
nav.className = "nav-lang"
45+
46+
Object.keys(languages).sort(function (a, b) {
47+
return languages[a] > languages[b] ? 1 : -1
48+
}).forEach(function(lang) {
49+
var button = createLangButton(dom, file, lang, languages[lang])
50+
items.push(button.firstChild)
51+
nav.appendChild(button)
52+
})
53+
var node = dom.querySelector('header > *:first-child')
54+
if (node) {
55+
node.parentNode.insertBefore(createSkipButton(dom), node)
56+
node.parentNode.insertBefore(nav, node)
57+
}
58+
return items
4459
}
4560

4661
function createLanguageLink(dom, href, locale) {
47-
var link = dom.createElement('link')
48-
link.setAttribute('rel', 'alternate')
49-
link.setAttribute('href', href)
50-
link.setAttribute('hreflang', locale)
51-
return link
62+
var link = dom.createElement('link')
63+
link.setAttribute('rel', 'alternate')
64+
link.setAttribute('href', href)
65+
link.setAttribute('hreflang', locale)
66+
return link
5267
}
5368

5469
function addLanguageLinks(dom, file, locales) {
55-
var head = dom.querySelector('head')
56-
return Object.keys(locales).map(function (locale) {
70+
var head = dom.querySelector('head')
71+
return Object.keys(locales).map(function (locale) {
5772
var link = createLanguageLink(dom, (locale != 'en' ? '/' + locale : '') + '/' + (file === 'index.html' ? '' : file), locale)
5873
head.appendChild(link)
5974
return link
60-
})
75+
})
6176
}
6277

6378
function getNodeByLanguage(nodes, lang) {
@@ -71,10 +86,54 @@ function getNodeByLanguage(nodes, lang) {
7186
}
7287
}
7388

89+
async function getStargazers(workshopElement) {
90+
const url = require('url');
91+
const githubUrl = workshopElement.getAttribute("href");
92+
let response, parsedUrl;
93+
94+
if (!githubUrl) {
95+
return 0;
96+
}
97+
98+
try {
99+
parsedUrl = url.parse(githubUrl);
100+
} catch (e) {
101+
throw e;
102+
}
103+
104+
try {
105+
response = await axios.get(`/repos${parsedUrl.path}?per_page=1`);
106+
} catch (e) {
107+
throw e;
108+
}
109+
writeStargazers(workshopElement, response.data.stargazers_count)
110+
}
111+
112+
function writeStargazers(workshopElement, stargazersCount) {
113+
const starIconSVG = `<svg style="vertical-align: middle" viewBox="0 0 16 16" version="1.1" width="16" height="16" role="img"><path fill="currentColor" fill-rule="evenodd" d="M14 6l-4.9-.64L7 1 4.9 5.36 0 6l3.6 3.26L2.67 14 7 11.67 11.33 14l-.93-4.74L14 6z"></path></svg>`;
114+
workshopElement.innerHTML = `
115+
<span>${workshopElement.innerHTML}</span>
116+
<span>(${starIconSVG}${kFormatter(stargazersCount)})<span>
117+
`;
118+
}
119+
120+
function kFormatter(number) {
121+
return number > 999 ? (number / 1000).toFixed(1) + 'k' : number
122+
}
123+
124+
async function isRateLimitReached(nbToPerform) {
125+
const remaining = await axios.get('/rate_limit')
126+
if (remaining.data.resources.core.remaining < nbToPerform) {
127+
console.error(`💔 GitHub Rate Limit Reached. Reset at ${new Date(remaining.data.resources.core.reset * 1000).toLocaleTimeString()}`);
128+
return true;
129+
}
130+
return false;
131+
}
132+
74133
cmdwatcher('build-html'
75-
, '!(node_modules).html'
76-
, 'languages/**'
77-
, function processFiles(files)
134+
, '!(node_modules).html'
135+
, 'languages/**'
136+
, function processFiles(files)
78137
{
79138
var languages
80139
try {
@@ -85,7 +144,7 @@ cmdwatcher('build-html'
85144
var translations = {}
86145
Object.keys(languages).forEach(function (lang) {
87146
var raw
88-
, translationPath = Path.join(__dirname, '../languages/', lang + '.json')
147+
, translationPath = Path.join(__dirname, '../languages/', lang + '.json')
89148
try {
90149
raw = Fs.readFileSync(translationPath, 'utf8')
91150
} catch (e) {
@@ -102,17 +161,26 @@ cmdwatcher('build-html'
102161
}
103162
})
104163
languages['en'] = 'English'
105-
files.forEach(function (file) {
164+
files.forEach(async function (file) {
106165
var raw
107-
, dom
108-
, original
166+
, dom
167+
, original
109168
try {
110169
raw = Fs.readFileSync(file, 'utf8')
111170
} catch(e) {
112171
return console.log('Error while reading %s:\n%s', file, e)
113172
}
114173
try {
115174
dom = jsdom.jsdom(raw)
175+
if (file === 'index.html') {
176+
const t0 = performance.now();
177+
const workshops = dom.getElementsByClassName('js-workshop-link');
178+
await isRateLimitReached(workshops.length);
179+
const githubPromises = Array.from(workshops).map(getStargazers);
180+
await Promise.all(githubPromises);
181+
const t1 = performance.now();
182+
console.log(`⭐️ GitHub Stargazers fetched in ${((t1 - t0) / 1000).toFixed(1)}s`)
183+
}
116184
} catch(e) {
117185
return console.log('Error while domify %s:\n%s', file, e)
118186
}
@@ -122,7 +190,7 @@ cmdwatcher('build-html'
122190
var footerHtml = Fs.readFileSync(FOOTER, 'utf8')
123191
footer.innerHTML = footerHtml
124192
}
125-
193+
126194
var list = dom.querySelectorAll('[data-i18n]')
127195
if (list) {
128196
original = {}
@@ -141,9 +209,9 @@ cmdwatcher('build-html'
141209

142210
Object.keys(languages).forEach(function (lang) {
143211
var translation = translations[lang]
144-
, outputPath = Path.join('.build/', (lang === 'en' ? '' : lang + '/'), file)
145-
, outputDir = Path.dirname(outputPath)
146-
, output
212+
, outputPath = Path.join('.build/', (lang === 'en' ? '' : lang + '/'), file)
213+
, outputDir = Path.dirname(outputPath)
214+
, output
147215
html.setAttribute('lang', lang)
148216
if (list) {
149217
try {

scripts/build.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ npm run build-chapters -- $@
33
npm run generate-css
44
npm run build-dependencies
55
npm run build-copy -- $@
6-
npm run build-html -- $@
6+
npm run build-html -- $@

scripts/deploy.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ rm -rf "$BUILD_FOLDER"
1414
# Checkout the current repo
1515
if [[ -z $GITHUB_PERSONAL_ACCESS_TOKEN ]]
1616
then
17-
git clone -b master git@github.com:nodeschool/nodeschool.github.io.git .build
17+
git clone -b master git@github.com:nodeschool/nodeschool.github.io.git .build
1818
else
19-
git clone -b master https://$GITHUB_USER:$GITHUB_PERSONAL_ACCESS_TOKEN@github.com/nodeschool/nodeschool.github.io.git .build
19+
git clone -b master https://$GITHUB_USER:$GITHUB_PERSONAL_ACCESS_TOKEN@github.com/nodeschool/nodeschool.github.io.git .build
2020
fi
2121

2222
npm run build

styles/style.styl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -455,7 +455,8 @@ span.no-break { display:inline-block; }
455455
font-family: Source Code Pro, monospace;
456456
font-weight: 500;
457457
text-decoration: none;
458-
display: block;
458+
display: flex;
459+
justify-content: space-between;
459460
border: none;
460461
}
461462

0 commit comments

Comments
 (0)