Skip to content

Commit aa135ee

Browse files
committed
deploy: 584c631
0 parents  commit aa135ee

File tree

3 files changed

+306
-0
lines changed

3 files changed

+306
-0
lines changed

.nojekyll

Whitespace-only changes.

events.json

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

index.html

Lines changed: 305 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,305 @@
1+
<!DOCTYPE html>
2+
<html lang="zh-CN">
3+
4+
<head>
5+
<meta charset="UTF-8">
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
7+
<title>GitHub 用户活动追踪</title>
8+
<style>
9+
body {
10+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
11+
max-width: 800px;
12+
margin: 20px auto;
13+
padding: 0 20px;
14+
background-color: #f6f8fa;
15+
}
16+
17+
.user-card {
18+
background: white;
19+
border: 1px solid #e1e4e8;
20+
border-radius: 6px;
21+
padding: 16px;
22+
margin-bottom: 20px;
23+
}
24+
25+
.user-header {
26+
display: flex;
27+
align-items: center;
28+
margin-bottom: 15px;
29+
}
30+
31+
.avatar {
32+
width: 40px;
33+
height: 40px;
34+
border-radius: 50%;
35+
margin-right: 10px;
36+
}
37+
38+
.username {
39+
font-size: 20px;
40+
font-weight: 600;
41+
color: #24292e;
42+
}
43+
44+
.event-item {
45+
padding: 8px 0;
46+
border-bottom: 1px solid #eaecef;
47+
display: flex;
48+
justify-content: space-between;
49+
align-items: center;
50+
}
51+
52+
.event-repo {
53+
color: #0366d6;
54+
font-weight: 500;
55+
margin-right: 1em;
56+
word-break: break-all;
57+
}
58+
59+
.event-repo+div {
60+
display: flex;
61+
flex-wrap: wrap;
62+
justify-content: flex-end;
63+
}
64+
65+
.event-type {
66+
background-color: #f3f4f6;
67+
padding: 2px 6px;
68+
margin: 4px;
69+
border-radius: 12px;
70+
font-size: 12px;
71+
color: #586069;
72+
}
73+
74+
.dropdown {
75+
position: relative;
76+
display: inline-block;
77+
padding-bottom: 20px;
78+
}
79+
80+
.dropdown-btn {
81+
background-color: #0366d6;
82+
color: white;
83+
padding: 8px 16px;
84+
border-radius: 6px;
85+
cursor: pointer;
86+
font-size: 14px;
87+
border: none;
88+
}
89+
90+
.dropdown-content {
91+
display: none;
92+
position: absolute;
93+
background-color: white;
94+
min-width: 200px;
95+
box-shadow: 0px 8px 16px rgba(0, 0, 0, 0.2);
96+
z-index: 1;
97+
margin-top: 5px;
98+
border-radius: 6px;
99+
}
100+
101+
.dropdown-content label {
102+
display: block;
103+
padding: 8px;
104+
cursor: pointer;
105+
font-size: 14px;
106+
}
107+
108+
.dropdown-content input {
109+
margin-right: 8px;
110+
}
111+
112+
.dropdown:hover .dropdown-content {
113+
display: block;
114+
}
115+
116+
.header {
117+
display: flex;
118+
justify-content: space-between;
119+
align-items: start;
120+
}
121+
</style>
122+
</head>
123+
124+
<body>
125+
<div class="header">
126+
<div class="dropdown">
127+
<button class="dropdown-btn">筛选事件</button>
128+
<div class="dropdown-content"></div>
129+
</div>
130+
<a href="https://github.com/liuly0322/github-follow-contributions" target="_blank"
131+
style="color: black; text-decoration: none;"><svg width="1.2em" height="1.2em" preserveAspectRatio="xMidYMid meet"
132+
viewBox="0 0 1024 1024" style="font-size: xx-large;">
133+
<path fill="currentColor"
134+
d="M511.6 76.3C264.3 76.2 64 276.4 64 523.5C64 718.9 189.3 885 363.8 946c23.5 5.9 19.9-10.8 19.9-22.2v-77.5c-135.7 15.9-141.2-73.9-150.3-88.9C215 726 171.5 718 184.5 703c30.9-15.9 62.4 4 98.9 57.9c26.4 39.1 77.9 32.5 104 26c5.7-23.5 17.9-44.5 34.7-60.8c-140.6-25.2-199.2-111-199.2-213c0-49.5 16.3-95 48.3-131.7c-20.4-60.5 1.9-112.3 4.9-120c58.1-5.2 118.5 41.6 123.2 45.3c33-8.9 70.7-13.6 112.9-13.6c42.4 0 80.2 4.9 113.5 13.9c11.3-8.6 67.3-48.8 121.3-43.9c2.9 7.7 24.7 58.3 5.5 118c32.4 36.8 48.9 82.7 48.9 132.3c0 102.2-59 188.1-200 212.9a127.5 127.5 0 0 1 38.1 91v112.5c.8 9 0 17.9 15 17.9c177.1-59.7 304.6-227 304.6-424.1c0-247.2-200.4-447.3-447.5-447.3z">
135+
</path>
136+
</svg></a>
137+
</div>
138+
<div id="container"></div>
139+
<script>
140+
const enumEventTypeStyles = {
141+
WatchEvent: { backgroundColor: '#e3fcec', color: '#026034' },
142+
PushEvent: { backgroundColor: '#ddf4ff', color: '#0550ae' },
143+
IssuesEvent: { backgroundColor: '#f1f8ff', color: '#0366d6' },
144+
PullRequestEvent: { backgroundColor: '#f0f8ff', color: '#0366d6' },
145+
PullRequestReviewEvent: { backgroundColor: '#f0f8ff', color: '#0366d6' },
146+
PullRequestReviewCommentEvent: { backgroundColor: '#f0f8ff', color: '#0366d6' },
147+
DeleteEvent: { backgroundColor: '#f1f8ff', color: '#0366d6' },
148+
IssueCommentEvent: { backgroundColor: '#f1f8ff', color: '#0366d6' },
149+
CreateEvent: { backgroundColor: '#f0f8ff', color: '#0366d6' },
150+
CommitCommentEvent: { backgroundColor: '#f1f8ff', color: '#0366d6' },
151+
MemberEvent: { backgroundColor: '#f0f8ff', color: '#0366d6' },
152+
ForkEvent: { backgroundColor: '#f0f8ff', color: '#0366d6' },
153+
ReleaseEvent: { backgroundColor: '#f0f8ff', color: '#0366d6' },
154+
GollumEvent: { backgroundColor: '#f0f8ff', color: '#0366d6' },
155+
PublicEvent: { backgroundColor: '#f1f8ff', color: '#0366d6' },
156+
}
157+
158+
const enumEventShortNames = {
159+
WatchEvent: 'Watch',
160+
PushEvent: 'Push',
161+
IssuesEvent: 'Issue',
162+
PullRequestEvent: 'PR',
163+
PullRequestReviewEvent: 'Review',
164+
PullRequestReviewCommentEvent: 'PRComment',
165+
DeleteEvent: 'Delete',
166+
IssueCommentEvent: 'IssueComment',
167+
CreateEvent: 'Create',
168+
CommitCommentEvent: 'CommitComment',
169+
MemberEvent: 'Member',
170+
ForkEvent: 'Fork',
171+
ReleaseEvent: 'Release',
172+
GollumEvent: 'Gollum',
173+
PublicEvent: 'Public',
174+
}
175+
176+
function createUserCard(username, events) {
177+
const card = document.createElement('div');
178+
card.className = 'user-card';
179+
180+
const header = document.createElement('div');
181+
header.className = 'user-header';
182+
183+
const avatar = document.createElement('img');
184+
avatar.className = 'avatar';
185+
avatar.src = `https://github.com/${username}.png`;
186+
avatar.alt = `${username}的头像`;
187+
188+
const name = document.createElement('div');
189+
name.className = 'username';
190+
name.textContent = username;
191+
192+
header.appendChild(avatar);
193+
header.appendChild(name);
194+
195+
const eventList = document.createElement('div');
196+
const eventMap = {};
197+
198+
events.forEach(event => {
199+
const [repoName, eventType] = event;
200+
if (!eventMap[repoName]) {
201+
eventMap[repoName] = [];
202+
}
203+
eventMap[repoName].push(eventType);
204+
});
205+
206+
for (const [repoName, eventTypes] of Object.entries(eventMap)) {
207+
const eventDiv = document.createElement('div');
208+
eventDiv.className = 'event-item';
209+
eventList.appendChild(eventDiv);
210+
211+
const repo = document.createElement('a');
212+
repo.className = 'event-repo';
213+
repo.href = `https://github.com/${repoName}`;
214+
repo.textContent = repoName;
215+
repo.target = '_blank';
216+
eventDiv.appendChild(repo);
217+
218+
const tagDiv = document.createElement('div');
219+
const tags = eventTypes.map(type => {
220+
const typeSpan = document.createElement('span');
221+
typeSpan.className = `event-type`;
222+
typeSpan.textContent = enumEventShortNames[type] || type;
223+
const style = enumEventTypeStyles[type] || {};
224+
Object.assign(typeSpan.style, style);
225+
return typeSpan;
226+
});
227+
tags.forEach(tag => tagDiv.appendChild(tag));
228+
eventDiv.appendChild(tagDiv);
229+
}
230+
231+
card.appendChild(header);
232+
card.appendChild(eventList);
233+
return card;
234+
}
235+
236+
function renderData(data) {
237+
const fragment = document.createDocumentFragment();
238+
data.forEach(userObj => {
239+
const username = Object.keys(userObj)[0];
240+
const events = userObj[username];
241+
if (events.length === 0) {
242+
return;
243+
}
244+
fragment.appendChild(createUserCard(username, events));
245+
});
246+
const container = document.getElementById('container');
247+
container.innerHTML = '';
248+
container.appendChild(fragment);
249+
}
250+
251+
function filterEvents(events, selectedEventTypes) {
252+
return events.filter(event => selectedEventTypes.includes(event[1]));
253+
}
254+
255+
function getSelectedEventTypes() {
256+
const checkboxes = document.querySelectorAll('.dropdown-content input');
257+
const selectedEventTypes = [];
258+
checkboxes.forEach(checkbox => {
259+
if (checkbox.checked) {
260+
selectedEventTypes.push(checkbox.value);
261+
}
262+
});
263+
return selectedEventTypes;
264+
}
265+
266+
const dropdownContent = document.querySelector('.dropdown-content');
267+
const dropdownFragment = document.createDocumentFragment();
268+
for (const [eventType, _] of Object.entries(enumEventTypeStyles)) {
269+
const label = document.createElement('label');
270+
const input = document.createElement('input');
271+
input.type = 'checkbox';
272+
input.value = eventType;
273+
input.id = eventType;
274+
input.checked = true;
275+
label.appendChild(input);
276+
label.appendChild(document.createTextNode(enumEventShortNames[eventType] || eventType));
277+
dropdownFragment.appendChild(label);
278+
}
279+
dropdownContent.appendChild(dropdownFragment);
280+
281+
function filterData(data) {
282+
const selectedEventTypes = getSelectedEventTypes();
283+
return data.map(userObj => {
284+
const username = Object.keys(userObj)[0];
285+
const events = userObj[username];
286+
return {
287+
[username]: filterEvents(events, selectedEventTypes)
288+
};
289+
});
290+
}
291+
292+
(async () => {
293+
const data = await fetch("events.json").then(res => res.json());
294+
renderData(filterData(data));
295+
296+
const dropdownInputs = document.querySelectorAll('.dropdown-content input');
297+
dropdownInputs.forEach(input => {
298+
input.addEventListener('change', () => {
299+
renderData(filterData(data));
300+
});
301+
});
302+
})()
303+
</script>
304+
</body>
305+
</html>

0 commit comments

Comments
 (0)