Github starred repositories
by Mladen Mihajlovic
HTML
<script src="https://unpkg.com/vue@next"></script>
<link rel="stylesheet" href="https://cdn.muicss.com/mui-0.10.3/css/mui.min.css">
<html>
<head></head>
<body>
<div id="app" class="mui-container">
<div class="mui-panel search-form">
<div class="mui-textfield "><input v-model="username" @keyup.enter="go"><label>Github username</label></div>
<button @click="go" :disabled="busy" class="mui-btn mui-btn--primary">
<svg v-if="busy" tooltip="Busy downloading pages..." class="spinner" viewBox="0 0 50 50">
<circle class="path" cx="25" cy="25" r="20" fill="none" stroke-width="5"></circle>
</svg><span v-else>Go</span></button>
</div>
<div class="mui-panel">
<a v-if="avatarUrl"><img width="32" :src="avatarUrl" style="float:right"></a>
<table>
<tr v-for="repo in repos" :key="repo.id">
<td><svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-repo mr-2 color-text-secondary flex-shrink-0">
<path fill-rule="evenodd" d="M2 2.5A2.5 2.5 0 014.5 0h8.75a.75.75 0 01.75.75v12.5a.75.75 0 01-.75.75h-2.5a.75.75 0 110-1.5h1.75v-2h-8a1 1 0 00-.714 1.7.75.75 0 01-1.072 1.05A2.495 2.495 0 012 11.5v-9zm10.5-1V9h-8c-.356 0-.694.074-1 .208V2.5a1 1 0 011-1h8zM5 12.25v3.25a.25.25 0 00.4.2l1.45-1.087a.25.25 0 01.3 0L8.6 15.7a.25.25 0 00.4-.2v-3.25a.25.25 0 00-.25-.25h-3.5a.25.25 0 00-.25.25z"></path>
</svg> <a :href="repo.html_url">{{repo.name}}</a> <span v-if="repo.description"> - {{ repo.description }}</span></td>
</tr>
</table>
</div>
</div>
</body>
</html>
CSS
body {
background: #eee;
}
.search-form {
margin-top: 15px;
display: flex;
}
.search-form .mui-textfield {
width: 100%;
}
.search-form .mui-btn {
/* flex: 1; */
}
td {
line-height: 16px;
display: flex;
align-items: center;
}
td svg {
margin-right: 5px;
flex-shrink: 0;
align-self: flex-start;
}
td a {
align-self: flex-start;
}
/* https://codepen.io/supah/pen/BjYLdW */
.spinner {
animation: rotate 2s linear infinite;
z-index: 2;
vertical-align: middle;
width: 16px;
height: 16px;
}
.spinner .path {
stroke: #fff;
stroke-linecap: round;
animation: dash 1.5s ease-in-out infinite;
}
@keyframes rotate {
100% {
transform: rotate(360deg);
}
}
@keyframes dash {
0% {
stroke-dasharray: 1, 150;
stroke-dashoffset: 0;
}
50% {
stroke-dasharray: 90, 150;
stroke-dashoffset: -35;
}
100% {
stroke-dasharray: 90, 150;
stroke-dashoffset: -124;
}
}
JavaScript
const ref = Vue.ref
const computed = Vue.computed
Vue.createApp({
setup() {
const username = ref(window.localStorage.getItem('username') || '')
const repos = ref([])
const busy = ref(false)
const avatarUrl = computed(() => username.value ? `https://github.com/${username.value}.png` : null)
const go = async () => {
busy.value = true
try {
let _getMore = true
let _page = 1
repos.value = []
while (_getMore) {
console.log('fetching page ' + _page)
const response = await fetch(`https://api.github.com/users/${username.value}/starred?per_page=500&page=${_page}`, {
headers: {
'Accept': 'application/vnd.github.v3+json'
}
});
const data = await response.json()
repos.value = repos.value.concat(data);
const _link = response.headers.get('link');
_page = _page + 1
_getMore = _link.includes('last'); // last appears on all BUT the last page
}
window.localStorage.setItem('username', username.value);
} catch (e) {
console.log(e.message)
repos.value = [{name:'error',description:e.message}]
}
busy.value = false
}
return {
username,
go,
repos,
busy,
avatarUrl
}
}
}).mount('#app')