Skip to content

Commit d208c59

Browse files
committed
Script and demo added. First version.
1 parent 67f0135 commit d208c59

File tree

2 files changed

+371
-0
lines changed

2 files changed

+371
-0
lines changed

ghrepocards.js

Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
async function processGHRepoCards() {
2+
3+
const GITHUB_API_SETTINGS_GHREPOCARDS = {
4+
method: 'GET',
5+
headers: {
6+
'Accept': 'application/vnd.github+json',
7+
'X-GitHub-Api-Version': '2022-11-28'
8+
}
9+
};
10+
11+
const GITHUB_API_ENDPOINT_GHREPOCARDS = "https://api.github.com/";
12+
13+
function saveDataLocal(repoData) {
14+
const now = new Date();
15+
let expirationHours = 1;
16+
const item = {
17+
repoData: repoData,
18+
expiry: now.getTime() + (expirationHours * 60 * 60 * 1000)
19+
};
20+
let key = repoData.name.toLowerCase();
21+
localStorage.setItem(key, JSON.stringify(item));
22+
}
23+
24+
function getDataLocal() {
25+
const items = {};
26+
let l = localStorage.length;
27+
for (let i = 0; i < l; i++) {
28+
const key = localStorage.key(i);
29+
const value = localStorage.getItem(key);
30+
try {
31+
const parsedValue = JSON.parse(value);
32+
if (parsedValue && typeof parsedValue === 'object') {
33+
const now = new Date();
34+
if (now.getTime() > parsedValue.expiry) {
35+
localStorage.removeItem(key);
36+
continue;
37+
}
38+
items[key] = parsedValue;
39+
}
40+
} catch (error) {
41+
localStorage.removeItem(key);
42+
throw new Error(`Error in getDataLocal(): ${error}.`);
43+
}
44+
}
45+
return items;
46+
}
47+
48+
function putData(repoData, cardDiv, userString) {
49+
let userData = repoData.owner;
50+
userData.userString = userString;
51+
52+
function appendTextContent(selector, content) {
53+
const elements = cardDiv.querySelectorAll(selector);
54+
elements.forEach(element => {
55+
element.textContent += content;
56+
});
57+
}
58+
59+
function setAttribute(selector, attr, value) {
60+
const elements = cardDiv.querySelectorAll(selector);
61+
elements.forEach(element => {
62+
element.setAttribute(attr, value);
63+
});
64+
}
65+
66+
appendTextContent(".gh-repo-cards-name", repoData.name);
67+
setAttribute(".gh-repo-cards-avatar", "src", userData.avatar_url);
68+
appendTextContent(".gh-repo-cards-username", userData.userString);
69+
appendTextContent(".gh-repo-cards-description", repoData.description || "");
70+
71+
const topicsContainers = cardDiv.querySelectorAll(".gh-repo-cards-topics");
72+
topicsContainers.forEach(topicsContainer => {
73+
if (repoData.repoTopics && repoData.repoTopics.length > 0) {
74+
topicsContainer.innerHTML += repoData.repoTopics.map(topic => `<li>${topic}</li>`).join('');
75+
}
76+
});
77+
78+
const languagesContainers = cardDiv.querySelectorAll(".gh-repo-cards-languages");
79+
languagesContainers.forEach(languagesContainer => {
80+
if (repoData.repoLanguages && Object.keys(repoData.repoLanguages).length > 0) {
81+
const sortedLanguages = Object.keys(repoData.repoLanguages)
82+
.sort((a, b) => repoData.repoLanguages[b] - repoData.repoLanguages[a]);
83+
languagesContainer.innerHTML += sortedLanguages.map(lang => `<li>${lang}</li>`).join('');
84+
}
85+
});
86+
87+
appendTextContent(".gh-repo-cards-stars", String(repoData.repoStars));
88+
appendTextContent(".gh-repo-cards-watchers", String(repoData.repoWatchers));
89+
appendTextContent(".gh-repo-cards-updatedat", repoData.repoUpdatedAt);
90+
appendTextContent(".gh-repo-cards-forks", String(repoData.repoForks));
91+
}
92+
93+
async function getData(query) {
94+
try {
95+
const response = await fetch(GITHUB_API_ENDPOINT_GHREPOCARDS + query, GITHUB_API_SETTINGS_GHREPOCARDS);
96+
if (!response.ok) {
97+
throw new Error(`GitHub API error! Status: ${response.status}.`);
98+
}
99+
return await response.json();
100+
} catch (error) {
101+
throw new Error(`Error in getData(): ${error}.`);
102+
}
103+
}
104+
105+
async function getAllRepos(userString, sortstring) {
106+
let allRepos = [];
107+
let page = 1;
108+
109+
while (true) {
110+
let repos = await getData(`users/${userString}/repos?per_page=100&page=${page}${sortstring}`);
111+
if (repos.length === 0) break;
112+
113+
const repoPromises = repos.map(async (repo) => {
114+
const [topics, languages, watchers] = await Promise.all([
115+
getData(`repos/${userString}/${repo.name}/topics`),
116+
getData(`repos/${userString}/${repo.name}/languages`),
117+
getData(`repos/${userString}/${repo.name}/subscribers`)
118+
]);
119+
120+
return {
121+
...repo,
122+
repoStars: repo.stargazers_count,
123+
repoUpdatedAt: repo.updated_at,
124+
repoForks: repo.forks_count,
125+
repoTopics: topics && topics.names ? topics.names : [],
126+
repoLanguages: languages || {},
127+
repoWatchers: watchers.length
128+
};
129+
});
130+
131+
allRepos.push(...await Promise.all(repoPromises));
132+
page++;
133+
}
134+
135+
return allRepos;
136+
}
137+
138+
async function getSingleRepo(userString, repoString) {
139+
let localRepos = getDataLocal();
140+
let repoData = localRepos[repoString];
141+
142+
if (repoData == undefined) {
143+
const [repo, topics, languages, watchers] = await Promise.all([
144+
getData(`repos/${userString}/${repoString}`),
145+
getData(`repos/${userString}/${repoString}/topics`),
146+
getData(`repos/${userString}/${repoString}/languages`),
147+
getData(`repos/${userString}/${repoString}/subscribers`)
148+
]);
149+
150+
repoData = {
151+
...repo,
152+
repoStars: repo.stargazers_count,
153+
repoUpdatedAt: repo.updated_at,
154+
repoForks: repo.forks_count,
155+
repoTopics: topics && topics.names ? topics.names : [],
156+
repoLanguages: languages || {},
157+
repoWatchers: watchers.length
158+
};
159+
160+
saveDataLocal(repoData);
161+
} else {
162+
repoData = repoData.repoData;
163+
}
164+
165+
return repoData;
166+
}
167+
168+
let cardsElements = [...document.getElementsByTagName("gh-repo-cards")];
169+
170+
if (cardsElements.length == 0) {
171+
console.warn(`GitHub-Repo-WebCards: No <gh-repo-cards> tag found.`);
172+
return;
173+
}
174+
175+
// Giving priority to %all.
176+
cardsElements.sort((a, b) => {
177+
const repoA = a.getAttribute('data-repo');
178+
const repoB = b.getAttribute('data-repo');
179+
if (repoA === '%all') return -1;
180+
if (repoB === '%all') return 1;
181+
return repoA.localeCompare(repoB);
182+
});
183+
184+
for (let cardTag of cardsElements) {
185+
const userString = cardTag.getAttribute("data-user")?.toLowerCase();
186+
const repoString = cardTag.getAttribute("data-repo")?.toLowerCase();
187+
188+
if (!userString) {
189+
throw new Error(`GitHub-Repo-WebCards: Invalid 'data-user' attribute in <gh-repo-cards> tag.`);
190+
}
191+
if (!repoString) {
192+
throw new Error(`GitHub-Repo-WebCards: Invalid 'data-repo' attribute in <gh-repo-cards> tag.`);
193+
}
194+
195+
const cardDiv = cardTag.querySelector(".gh-repo-cards-div");
196+
if (!cardDiv) {
197+
throw new Error(`GitHub-Repo-WebCards: Missing div with class 'gh-repo-cards-div' in <gh-repo-cards> tag, at least 1 must be present.`);
198+
}
199+
200+
if (repoString === "%all") {
201+
// All repos.
202+
const reposort = cardTag.getAttribute("data-sort")?.toLowerCase();
203+
const directionsort = cardTag.getAttribute("data-direction")?.toLocaleLowerCase();
204+
let sortstring = "";
205+
206+
if (reposort) {
207+
const admittedsort = ['created', 'updated', 'pushed', 'full_name'];
208+
const admitteddirection = ['asc', 'desc'];
209+
210+
if (!admittedsort.includes(reposort)) {
211+
throw new Error(`GitHub-Repo-WebCards: Invalid 'data-sort' attribute. Must be one of: ${admittedsort.join(', ')}.`);
212+
}
213+
if (directionsort && !admitteddirection.includes(directionsort)) {
214+
throw new Error(`GitHub-Repo-WebCards: Invalid 'data-direction' attribute. Must be 'asc' or 'desc'.`);
215+
}
216+
217+
sortstring = `&sort=${reposort}&direction=${directionsort}`;
218+
}
219+
220+
const localRepos = getDataLocal();
221+
const redownload = !Object.values(localRepos).some(repo => repo.repoData.owner.login.toLowerCase() === userString);
222+
223+
let repolist;
224+
if (redownload) {
225+
repolist = await getAllRepos(userString, sortstring);
226+
repolist.forEach(saveDataLocal);
227+
} else {
228+
repolist = Object.values(localRepos).map(e => e.repoData).filter(e => e.owner.login.toLowerCase() === userString);
229+
}
230+
231+
const originalContent = cardDiv.innerHTML;
232+
233+
repolist.forEach((repoFromList, index) => {
234+
const newCardDiv = cardDiv.cloneNode(true);
235+
newCardDiv.innerHTML = originalContent;
236+
putData(repoFromList, newCardDiv, userString);
237+
238+
if (index === 0) {
239+
cardDiv.innerHTML = newCardDiv.innerHTML;
240+
} else {
241+
cardTag.appendChild(newCardDiv);
242+
}
243+
});
244+
245+
} else {
246+
// Single repo.
247+
const repoData = await getSingleRepo(userString, repoString);
248+
putData(repoData, cardDiv, userString);
249+
}
250+
251+
cardTag.style.display = "block";
252+
}
253+
254+
processGHRepoCards.saveDataLocal = saveDataLocal;
255+
processGHRepoCards.getDataLocal = getDataLocal;
256+
processGHRepoCards.putData = putData;
257+
processGHRepoCards.getData = getData;
258+
processGHRepoCards.getAllRepos = getAllRepos;
259+
processGHRepoCards.getSingleRepo = getSingleRepo;
260+
261+
}
262+
263+
processGHRepoCards();
264+

index.html

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>GitHub-Repo-WebCards Demo</title>
7+
<style>
8+
body {
9+
text-align: center;
10+
}
11+
ul {
12+
display: inline-block;
13+
text-align: left;
14+
}
15+
.hr-big {
16+
height: 4px;
17+
background-color: black;
18+
}
19+
.hr-small {
20+
height: 2px;
21+
background-color: blue;
22+
width: 20vw;
23+
}
24+
</style>
25+
</head>
26+
<body>
27+
28+
<h1>This is just an example of how GitHub-Repo-WebCards works (demo).</h1>
29+
30+
<h2>Single repo's data:</h2>
31+
32+
<gh-repo-cards style="display: none;" data-user="JULIUSNIXI" data-repo="bOgGle">
33+
34+
<div class="gh-repo-cards-div">
35+
36+
<h3>Repo's data:</h3>
37+
<p class="gh-repo-cards-name">Repo's name: </p>
38+
<p class="gh-repo-cards-description">Repo's description: </p>
39+
40+
</div>
41+
42+
</gh-repo-cards>
43+
44+
<br /> <br />
45+
<hr class="hr-big" />
46+
<br /> <br />
47+
48+
<h2>Single repo's data:</h2>
49+
50+
<gh-repo-cards style="display: none;" data-user="withastro" data-repo="astro">
51+
52+
<div class="gh-repo-cards-div">
53+
54+
<h3>Owner's data:</h3>
55+
<img width="50px" height="50px" class="gh-repo-cards-avatar" />
56+
<p class="gh-repo-cards-username">Owner's username: </p>
57+
58+
<h3>Repo's data:</h3>
59+
<p class="gh-repo-cards-name">Repo's name: </p>
60+
<p class="gh-repo-cards-description">Repo's description: </p>
61+
<ul class="gh-repo-cards-topics">Repo's topics: </ul>
62+
<ul class="gh-repo-cards-languages">Repo's languages: </ul>
63+
<p class="gh-repo-cards-stars">Repo's stars: </p>
64+
<p class="gh-repo-cards-watchers">Repo's watchers: </p>
65+
<p class="gh-repo-cards-updatedat">Repo updated at: </p>
66+
<p class="gh-repo-cards-forks">Repo's forks: </p>
67+
68+
</div>
69+
70+
</gh-repo-cards>
71+
72+
<br /> <br />
73+
<hr class="hr-big" />
74+
<br /> <br />
75+
76+
<h2>Bulk (all) repo's data:</h2>
77+
78+
<gh-repo-cards style="display: none;" data-user="JuliusNixi" data-repo="%all" data-sort="created" data-direction="desc">
79+
80+
<div class="gh-repo-cards-div">
81+
82+
<h3>Owner's data:</h3>
83+
<img width="50px" height="50px" class="gh-repo-cards-avatar" />
84+
<p class="gh-repo-cards-username">Owner's username: </p>
85+
86+
<h3>Repo's data:</h3>
87+
<p class="gh-repo-cards-name">Repo's name: </p>
88+
<p class="gh-repo-cards-description">Repo's description: </p>
89+
<ul class="gh-repo-cards-topics">Repo's topics: </ul>
90+
<ul class="gh-repo-cards-languages">Repo's languages: </ul>
91+
<p class="gh-repo-cards-stars">Repo's stars: </p>
92+
<p class="gh-repo-cards-watchers">Repo's watchers: </p>
93+
<p class="gh-repo-cards-updatedat">Repo updated at: </p>
94+
<p class="gh-repo-cards-forks">Repo's forks: </p>
95+
96+
<hr class="hr-small" />
97+
98+
</div>
99+
100+
</gh-repo-cards>
101+
102+
</body>
103+
104+
<script src="ghrepocards.js"></script>
105+
106+
</html>
107+

0 commit comments

Comments
 (0)