Skip to content

Commit bb4d1b1

Browse files
committed
feat: add github stats
1 parent 00ff67f commit bb4d1b1

File tree

8 files changed

+163
-105
lines changed

8 files changed

+163
-105
lines changed

docs/src/components/AnswerNumber.astro

Lines changed: 0 additions & 15 deletions
This file was deleted.

docs/src/components/ChallengeFooter.astro

Lines changed: 3 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import VideoButton from './VideoButton.astro';
33
import ClipboardCopy from './ClipboardCopy.astro';
44
import { getEntry } from 'astro:content';
5+
import AnsweredUser from './github/AnsweredUser.svelte';
56
67
const { lang } = Astro.props;
78
const { author, challengeNumber, title, blogLink, videoLink, command } = Astro.props.entry.data;
@@ -11,27 +12,6 @@ const authorLink = `https://github.com/tomalaforge/angular-challenges/pulls?q=la
1112
const communityLink = `https://github.com/tomalaforge/angular-challenges/pulls?q=label%3A${challengeNumber}+label%3Aanswer+sort%3Areactions-%2B1-desc`;
1213
const npxCommand = `npx nx serve ${command}`;
1314
14-
let items = [];
15-
let page = 1;
16-
let error = false;
17-
try {
18-
while (true) {
19-
const response = await fetch(`https://api.github.com/search/issues?q=repo:tomalaforge/angular-challenges+is:pr+label:"${challengeNumber}"+label:"answer"&per_page=100&page=${page}`);
20-
if (!response.ok) {
21-
throw new Error('Network response was not ok');
22-
}
23-
const { items: new_items, total_count } = await response.json();
24-
if (!new_items || new_items.length === 0) break;
25-
items.push(...new_items);
26-
27-
if(total_count < page * 100) break;
28-
29-
page++;
30-
}
31-
} catch (e) {
32-
error = true;
33-
}
34-
3515
---
3616

3717
<div class="separator"></div>
@@ -71,7 +51,7 @@ while (true) {
7151
<div class="article-footer">
7252
<a
7353
href={communityLink}>
74-
{items.length > 0 ? items.length : ''} {data['challenge.footer.communityAnswers']}*
54+
{data['challenge.footer.communityAnswers']}*
7555
</a>
7656
<a
7757
href={authorLink}>
@@ -92,20 +72,7 @@ while (true) {
9272
<VideoButton {...videoLink} {...Astro.props} />}
9373
</div>
9474

95-
{error ? null :
96-
<div class="solution-container">
97-
<div>Answered by</div>
98-
{(items ?? []).map((item) => (
99-
<img
100-
loading="lazy"
101-
src={item.user.avatar_url}
102-
width="30"
103-
height="30"
104-
alt=""
105-
class="avatar"
106-
/>
107-
))}
108-
</div>}
75+
<AnsweredUser client:load {challengeNumber} />
10976

11077
<div class="footer-note">
11178
* {data['challenge.footer.upvoteAnswer']}
@@ -131,16 +98,5 @@ while (true) {
13198
margin-top: 3rem;
13299
}
133100

134-
135-
.solution-container {
136-
margin-top: 3rem;
137-
display: flex;
138-
flex-wrap: wrap;
139-
gap: 0.5rem;
140-
141-
img {
142-
border-radius: 50%;
143-
}
144-
}
145101
</style>
146102

docs/src/components/Content.astro

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import Author from './Author.astro';
66
import ChallengeFooter from './ChallengeFooter.astro';
77
import CommentSection from './CommentSection.astro';
88
import ContributorsFooter from './ContributorsFooter.astro';
9-
import AnswerNumber from './AnswerNumber.astro';
9+
import AnswerNumber from './github/AnswerNumber.svelte';
1010
1111
const { lang } = Astro.props;
1212
const { data } = await getEntry('i18n', lang);
@@ -18,7 +18,7 @@ const renderCommentSection = !Astro.props.entry.data.noCommentSection;
1818

1919
{ challengeNumber ? <div class="header-container">
2020
{ author ? <Author {...author.data} {data} /> : null}
21-
<AnswerNumber {challengeNumber}/>
21+
<AnswerNumber client:load/>
2222
</div> :null}
2323

2424
<Default {...Astro.props}>
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<script>
2+
import MyIcon from './MyIcon.astro';
3+
import { onMount } from 'svelte';
4+
5+
let error = false;
6+
let loading = true;
7+
let stargazersCount = 0;
8+
let forksCount = 0;
9+
10+
11+
async function fetchStats() {
12+
try {
13+
const response = await fetch(`https://api.github.com/repos/tomalaforge/angular-challenges`);
14+
if (!response.ok) {
15+
throw new Error('Network response was not ok');
16+
}
17+
const { stargazers_count, forks } = await response.json();
18+
stargazersCount = stargazers_count;
19+
forksCount = forks;
20+
21+
} catch (e) {
22+
error = true;
23+
} finally {
24+
loading = false;
25+
}
26+
}
27+
28+
onMount(() => {
29+
fetchStats();
30+
});
31+
</script>
32+
33+
{#if !error && !loading}
34+
<div class="github">
35+
<a class="category" href="https://github.com/tomalaforge/angular-challenges">
36+
<MyIcon name="star" />
37+
<div>{stargazersCount}</div>
38+
</a>
39+
40+
<div class="category fork">
41+
<MyIcon name="fork" viewBox="0 0 16 16" />
42+
<div>{forksCount}</div>
43+
</div>
44+
</div>
45+
{/if}
46+
47+
<style>
48+
.github {
49+
display: flex;
50+
flex-direction: column;
51+
align-items: flex-start;
52+
margin-left: var(--sl-nav-gap)
53+
}
54+
55+
.category {
56+
display: flex;
57+
align-items: center;
58+
font-size: 12px;
59+
gap: 0.25rem;
60+
}
61+
62+
</style>
Lines changed: 2 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,13 @@
11
---
2-
import { getEntry } from 'astro:content';
3-
import type { Props } from '@astrojs/starlight/props';
42
import Default from '@astrojs/starlight/components/SiteTitle.astro';
5-
import MyIcon from './MyIcon.astro';
6-
const { challengeNumber } = Astro.props.entry.data;
7-
const { lang } = Astro.props;
8-
const { data } = await getEntry('i18n', lang);
9-
10-
const response = await fetch(`https://api.github.com/repos/tomalaforge/angular-challenges`);
11-
const { stargazers_count, forks } = await response.json();
12-
13-
14-
3+
import GitHubStats from './GitHubStats.svelte';
154
165
---
176

187
<Default {...Astro.props}>
198
<slot />
209
</Default>
2110

22-
<div class="github">
23-
<a class="category" href="https://github.com/tomalaforge/angular-challenges">
24-
<MyIcon name="star" />
25-
<div>{stargazers_count}</div>
26-
</a>
27-
28-
<div class="category fork">
29-
<MyIcon name="fork" viewBox="0 0 16 16" />
30-
<div>{forks}</div>
31-
</div>
32-
</div>
33-
34-
<style>
35-
.github {
36-
display: flex;
37-
flex-direction: column;
38-
align-items: flex-start;
39-
margin-left: var(--sl-nav-gap)
40-
}
11+
<GitHubStats client:load />
4112

42-
.category {
43-
display: flex;
44-
align-items: center;
45-
font-size: 12px;
46-
gap: 0.25rem;
47-
}
4813

49-
.fork {
50-
//margin-top: -5px;
51-
}
52-
</style>
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<script>
2+
import { isLoaded, totalCount } from './github-store';
3+
</script>
4+
5+
{#if isLoaded}
6+
<div class="answer-text">Answered by {$totalCount} people</div>
7+
{/if}
8+
9+
<style>
10+
.answer-text {
11+
font-size: var(--sl-text-xs);
12+
color: var(--sl-color-gray-3);
13+
width: max-content;
14+
}
15+
</style>
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<script>
2+
import { onMount } from 'svelte';
3+
import { data, error, isLoaded, isLoading, totalCount } from './github-store';
4+
5+
export let challengeNumber;
6+
7+
let page = 1;
8+
9+
async function fetchTotalCount() {
10+
isLoading.set(true);
11+
try {
12+
while (true) {
13+
const response = await fetch(`https://api.github.com/search/issues?q=repo:tomalaforge/angular-challenges+is:pr+label:"${challengeNumber}"+label:"answer"&per_page=100&page=${page}`);
14+
if (!response.ok) {
15+
throw new Error('Network response was not ok');
16+
}
17+
const { items: new_items, total_count } = await response.json();
18+
if (!new_items || new_items.length === 0) break;
19+
data.update(items => [...items, ...new_items]);
20+
totalCount.set(total_count);
21+
22+
if(total_count < page * 100) break;
23+
24+
page++;
25+
}
26+
} catch (e) {
27+
error.set(true);
28+
} finally {
29+
isLoading.set(false);
30+
}
31+
}
32+
33+
onMount(() => {
34+
fetchTotalCount();
35+
});
36+
37+
</script>
38+
39+
{#if $isLoaded}
40+
<div class="solution-container">
41+
<div>Answered by</div>
42+
{#each $data as { user }}
43+
<img
44+
loading="lazy"
45+
src={user.avatar_url}
46+
width="30"
47+
height="30"
48+
alt=""
49+
class="avatar"
50+
/>
51+
{/each}
52+
</div>
53+
{/if}
54+
55+
<style>
56+
.solution-container {
57+
margin-top: 3rem;
58+
display: flex;
59+
flex-wrap: wrap;
60+
gap: 0.5rem;
61+
62+
}
63+
.avatar {
64+
border-radius: 50%;
65+
}
66+
</style>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { derived, writable } from 'svelte/store';
2+
3+
export const count = writable(0);
4+
5+
export const isLoading = writable(true);
6+
export const error = writable(false);
7+
export const data = writable<any[]>([]);
8+
export const totalCount = writable(0);
9+
10+
export const isLoaded = derived(
11+
[isLoading, error],
12+
([$isLoading, $error]) => !$isLoading && !$error,
13+
);

0 commit comments

Comments
 (0)