Skip to content

Commit d2623d9

Browse files
committed
feat: add CodePenExample and LiveDemo components for interactive documentation
feat: implement content configuration for Astro documentation docs: create comprehensive API documentation for configuration options and reference docs: add code examples for common use cases of RepoWidget docs: introduce live demo page for interactive configuration testing docs: provide installation instructions for CDN, npm, and ES module imports docs: write introduction and quick start guide for RepoWidget docs: update changelog with recent changes and improvements docs: outline contributing guidelines for RepoWidget development chore: add TypeScript configuration for documentation
1 parent d8e011f commit d2623d9

34 files changed

Lines changed: 1897 additions & 4924 deletions

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
node_modules/
22
package-lock.json
33
.vercel
4-
dist
54
.DS_Store
5+
docs/node_modules/
6+
docs/dist/
7+
docs/.astro/
8+
update-documentation.md

build.js

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,17 +31,14 @@ async function build() {
3131
.replace('})();', '');
3232
fs.writeFileSync(path.join(__dirname, 'dist/repoWidget.esm.js'), esmCode);
3333

34-
// Copy to docs
34+
// Copy minified build to docs public so the dev server and deployed site can serve it
35+
fs.ensureDirSync(path.join(__dirname, 'docs/public'));
3536
fs.copyFileSync(
3637
path.join(__dirname, 'dist/repoWidget.min.js'),
37-
path.join(__dirname, 'docs/assets/js/repoWidget.min.js')
38-
);
39-
fs.copyFileSync(
40-
path.join(__dirname, 'src/lib/repoWidget.js'),
41-
path.join(__dirname, 'docs/assets/js/repoWidget.js')
38+
path.join(__dirname, 'docs/public/repoWidget.min.js')
4239
);
4340

44-
console.log('Build complete. Files written to dist/ and docs/assets/js/');
41+
console.log('Build complete. Files written to dist/ and docs/public/');
4542
}
4643

4744
build().catch(err => {

dist/repoWidget.esm.js

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
/**
2+
* RepoWidget - A professional GitHub repository display widget
3+
* @version 1.1.0
4+
* @author Peter Benoit
5+
* @license MIT
6+
*/
7+
// ESM version of RepoWidget
8+
export function createRepoWidget {
9+
function createRepoWidget({
10+
username, // GitHub username
11+
containerId, // ID of the container element
12+
columns = { mobile: 1, tablet: 2, desktop: 3 },
13+
cardStyles = {}, // Optional custom styles for the card background and container
14+
textStyles = {}, // Optional custom styles for text and icon colors
15+
scaleOnHover = 1.05, // Default scale factor on hover; set to 0 or false to disable
16+
maxRepos = columns.desktop === 1 ? 10 : columns.desktop * 2, // Default maxRepos is double the desktop column count
17+
sortBy = 'stars', // Sorting parameter; options: "stars", "forks", "size", "name", "updated"
18+
exclude = [], // Array of repository names to exclude
19+
}) {
20+
const repoContainer = document.getElementById(containerId);
21+
22+
const languageColors = {
23+
JavaScript: '#f1e05a',
24+
Python: '#3572A5',
25+
TypeScript: '#2b7489',
26+
Vue: '#41b883',
27+
React: '#61DAFB',
28+
Angular: '#E53238',
29+
Node: '#339933',
30+
Express: '#000000',
31+
Django: '#092E20',
32+
CSS: '#563d7c',
33+
HTML: '#e34c26',
34+
Java: '#b07219',
35+
C: '#555555',
36+
'C#': '#178600',
37+
'C++': '#f34b7d',
38+
Go: '#00add8',
39+
Ruby: '#701516',
40+
PHP: '#4F5D95',
41+
Swift: '#ffac45',
42+
Kotlin: '#F18E33',
43+
Rust: '#dea584',
44+
SQL: '#e38c00',
45+
MySQL: '#4479A1',
46+
PostgreSQL: '#336791',
47+
MongoDB: '#47A248',
48+
Docker: '#2496ED',
49+
GitHub: '#181717',
50+
Azure: '#0078D4',
51+
AWS: '#FF9900',
52+
};
53+
54+
repoContainer.style.display = 'grid';
55+
repoContainer.style.gap = '16px';
56+
57+
const styles = `
58+
#${containerId} {
59+
grid-template-columns: repeat(${columns.mobile}, 1fr);
60+
}
61+
@media (min-width: 640px) {
62+
#${containerId} {
63+
grid-template-columns: repeat(${columns.tablet}, 1fr);
64+
}
65+
}
66+
@media (min-width: 1024px) {
67+
#${containerId} {
68+
grid-template-columns: repeat(${columns.desktop}, 1fr);
69+
}
70+
}
71+
`;
72+
73+
const styleSheet = document.createElement('style');
74+
styleSheet.innerText = styles;
75+
document.head.appendChild(styleSheet);
76+
77+
// Cache response for 1 day
78+
const CACHE_EXPIRATION = 24 * 60 * 60 * 1000;
79+
80+
async function fetchRepos() {
81+
const cacheKey = `repos_${username}`;
82+
const cachedData = localStorage.getItem(cacheKey);
83+
const cachedETag = localStorage.getItem(`${cacheKey}_etag`);
84+
const cacheTimestamp = localStorage.getItem(`${cacheKey}_timestamp`);
85+
const now = Date.now();
86+
const headers = {};
87+
88+
if (cachedETag) {
89+
headers['If-None-Match'] = cachedETag;
90+
}
91+
92+
const response = await fetch(`https://api.github.com/users/${username}/repos`, {
93+
headers,
94+
});
95+
96+
if (response.status === 304 && cachedData) {
97+
return JSON.parse(cachedData);
98+
}
99+
100+
if (!response.ok) {
101+
console.error('GitHub API error:', response.statusText);
102+
return [];
103+
}
104+
105+
const repos = await response.json();
106+
const eTag = response.headers.get('ETag');
107+
108+
localStorage.setItem(cacheKey, JSON.stringify(repos));
109+
localStorage.setItem(`${cacheKey}_timestamp`, now);
110+
if (eTag) {
111+
localStorage.setItem(`${cacheKey}_etag`, eTag);
112+
}
113+
114+
return repos;
115+
}
116+
117+
// Sort repositories based on the provided sortBy parameter
118+
function sortRepositories(repos) {
119+
switch (sortBy) {
120+
case 'stars':
121+
return repos.sort((a, b) => b.stargazers_count - a.stargazers_count);
122+
case 'forks':
123+
return repos.sort((a, b) => b.forks_count - a.forks_count);
124+
case 'size':
125+
return repos.sort((a, b) => b.size - a.size);
126+
case 'name':
127+
return repos.sort((a, b) => a.name.localeCompare(b.name));
128+
case 'updated':
129+
return repos.sort((a, b) => new Date(b.updated_at) - new Date(a.updated_at));
130+
default:
131+
return repos;
132+
}
133+
}
134+
135+
async function initializeWidget() {
136+
let repos = await fetchRepos();
137+
138+
if (exclude && exclude.length > 0) {
139+
repos = repos.filter((repo) => !exclude.includes(repo.name));
140+
}
141+
142+
repos = sortRepositories(repos).slice(0, maxRepos);
143+
144+
const fragment = document.createDocumentFragment();
145+
146+
repos.forEach((repo) => {
147+
const card = document.createElement('article');
148+
card.setAttribute('role', 'region');
149+
card.setAttribute('aria-labelledby', `repo-title-${repo.name}`);
150+
card.style.cssText = `
151+
background: #fff;
152+
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
153+
border-radius: 8px;
154+
overflow: hidden;
155+
transition: transform 0.3s;
156+
`;
157+
158+
Object.assign(card.style, cardStyles);
159+
160+
if (scaleOnHover) {
161+
card.onmouseover = () => (card.style.transform = `scale(${scaleOnHover})`);
162+
card.onmouseleave = () => (card.style.transform = 'scale(1)');
163+
}
164+
165+
const languageColor = languageColors[repo.language] || '#cccccc';
166+
167+
card.innerHTML = `
168+
<a href="${repo.html_url
169+
}" target="_blank" style="text-decoration: none; color: inherit; display: flex; flex-direction: column; height: 100%; padding: 16px;" aria-label="Repository ${repo.name
170+
}">
171+
<div style="flex: 1;">
172+
<h3 id="repo-title-${repo.name
173+
}" style="font-size: 1.25rem; font-weight: bold; color: ${textStyles.titleColor || '#333333'
174+
};">${repo.name}</h3>
175+
<p style="color: ${textStyles.descriptionColor || '#666666'}; margin: 8px 0;">${repo.description || 'No description provided'
176+
}</p>
177+
</div>
178+
<div style="margin-top: auto;">
179+
<div style="display: flex; align-items: center; color: ${textStyles.iconColor || '#888888'
180+
}; font-size: 0.875rem;">
181+
<span style="display: flex; align-items: center; margin-right: 16px;">
182+
<span style="width: 10px; height: 10px; background-color: ${languageColor}; border-radius: 50%; margin-right: 4px;"></span>
183+
${repo.language || 'N/A'}
184+
</span>
185+
<span style="display: flex; align-items: center; margin-right: 16px;">
186+
<svg width="16" height="16" fill="${textStyles.iconColor || '#888888'
187+
}" style="margin-right: 4px;"><path d="M5 5.372v.878c0 .414.336.75.75.75h4.5a.75.75 0 0 0 .75-.75v-.878a2.25 2.25 0 1 1 1.5 0v.878a2.25 2.25 0 0 1-2.25 2.25h-1.5v2.128a2.251 2.251 0 1 1-1.5 0V8.5h-1.5A2.25 2.25 0 0 1 3.5 6.25v-.878a2.25 2.25 0 1 1 1.5 0ZM5 3.25a.75.75 0 1 0-1.5 0 .75.75 0 0 0 1.5 0Zm6.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Zm-3 8.75a.75.75 0 1 0-1.5 0 .75.75 0 0 0 1.5 0Z"></path></svg>
188+
${repo.forks_count}
189+
</span>
190+
<span style="display: flex; align-items: center;">
191+
<svg width="16" height="16" fill="${textStyles.iconColor || '#888888'
192+
}" style="margin-right: 4px;"><path d="M8 .25a.75.75 0 0 1 .673.418l1.882 3.815 4.21.612a.75.75 0 0 1 .416 1.279l-3.046 2.97.719 4.192a.751.751 0 0 1-1.088.791L8 12.347l-3.766 1.98a.75.75 0 0 1-1.088-.79l.72-4.194L.818 6.374a.75.75 0 0 1 .416-1.28l4.21-.611L7.327.668A.75.75 0 0 1 8 .25Z"></path></svg>
193+
${repo.stargazers_count}
194+
</span>
195+
</div>
196+
<div style="color: ${textStyles.sizeColor || '#aaaaaa'
197+
}; font-size: 0.75rem; margin-top: 8px;">Size: ${repo.size} KB</div>
198+
</div>
199+
</a>
200+
`;
201+
202+
fragment.appendChild(card);
203+
});
204+
205+
repoContainer.innerHTML = '';
206+
repoContainer.appendChild(fragment);
207+
}
208+
209+
initializeWidget();
210+
}
211+
212+
213+

0 commit comments

Comments
 (0)