我可以获得我的要点清单吗?
这样的列表将列出所有要点,而不仅仅是四个要点,并且在我单击它之前不会显示要点的内容。
有一个简单的方法: https://gist.github.com/anders
只需将用户名放在网址末尾即可:gist.github.com/[用户名]
使用 GitHub Gist API:https://developer.github.com/v3/gists/#list-a-users-gists
例如,列出用户
anders
的所有公共Gist:https://api.github.com/users/anders/gists
部分返回结果:
"html_url": "https://gist.github.com/5b2bd534992dafe3f05202d43d8e48a2",
然后访问:https://gist.github.com/5b2bd534992dafe3f05202d43d8e48a2你将看到这个特定gist的详细内容。
这个答案正确地建议使用免费的、未经身份验证的、支持cors的API检索要点信息:
https://api.github.com/users/${username}/gists
。
但是,答案没有提到初始请求仅返回第一页。要获取用户的所有公共要点,请迭代页面,直到看到空数组响应(我想如果有任何返回小于最大页面大小(似乎为 30),您可以保存请求)。为了方便起见,下面是一个 JS 示例:
const fetchJson = (...args) =>
fetch(...args).then(response => {
if (!response.ok) {
throw Error(response.status);
}
return response.json();
});
const fetchGists = async (username, maxPages=10) => {
const gists = [];
for (let page = 1; page <= maxPages; page++) {
const url = `https://api.github.com/users/${username}/gists?page=${page}`;
const chunk = await fetchJson(url);
if (chunk.length === 0) {
break;
}
gists.push(...chunk);
}
return gists;
};
fetchGists("gaearon").then(gists => {
// for all fields:
//document.body.textContent = JSON.stringify(gists, null, 2);
// ...or select interesting fields:
gists = gists.map(({
description, files, html_url, created_at, updated_at
}) => ({
description, files, html_url, created_at, updated_at
})).sort((a, b) => b.updated_at.localeCompare(a.updated_at));
document.body.textContent =
`total number of public gists: ${gists.length}
${JSON.stringify(gists, null, 2)}`;
});
body {
white-space: pre;
font-family: monospace;
}
注意:撰写本文时的速率限制为每小时 60 个请求,很容易超出。您可以在响应标头中监控当前的速率限制:
x-ratelimit-limit: 60
x-ratelimit-remaining: 0
x-ratelimit-reset: 1664391218
x-ratelimit-resource: core
x-ratelimit-used: 60
这是一个更全面的示例,显示了速率限制和要点以及更漂亮的界面。我在 GitHub 页面上托管一个版本,这样我就可以轻松找到我的要点(内置的 GitHub 网站要点页面很难一次点击 10 个项目,并且搜索功能很差):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Gist List</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.1.0/github-markdown-dark.min.css" crossorigin="anonymous" referrerpolicy="no-referrer" />
<style>
body {
margin: 0;
padding: 2.5em;
box-sizing: border-box;
}
.markdown-body {
min-width: 200px;
max-width: 980px;
margin: 0 auto;
}
.github-limits {
margin-top: 1em;
margin-bottom: 2em;
}
.gists ul {
list-style-type: none;
padding: 0 !important;
margin: 0 !important;
}
.gists th {
cursor: pointer;
position: relative;
}
.gists .sorted-desc:after {
position: absolute;
right: 0.5em;
content: "\25BC";
font-size: 0.8em;
}
.gists .sorted-asc:after {
position: absolute;
right: 0.5em;
content: "\25B2";
font-size: 0.8em;
}
.github-logo {
position: absolute;
right: 1.5em;
top: 1.5em;
}
td, th {
min-width: 75px;
font-size: 0.9em;
}
@media (max-width: 767px) {
.markdown-body {
padding: 15px;
}
}
@media (max-width: 467px) {
.markdown-body {
padding: 10px;
}
td, th {
min-width: 70px;
font-size: 0.8em;
}
}
</style>
</head>
<body class="markdown-body">
<div class="github-logo">
<a href="https://github.com/ggorlen/gist-list">
<img src="https://github.githubassets.com/images/modules/site/icons/footer/github-mark.svg" alt="GitHub mark" width="20" height="20">
</a>
</div>
<h1>Gist List</h1>
<div>
<form class="gist-search">
<input
class="github-username"
placeholder="Enter a GitHub username"
autofocus
>
<input type="submit" value="search">
</form>
</div>
<div class="github-limits"></div>
<div class="gists"></div>
<script>
"use strict";
class GHRequestError extends Error {
constructor(limits = {}, ...params) {
super(...params);
if (Error.captureStackTrace) {
Error.captureStackTrace(this, GHRequestError);
}
this.name = "GHRequestError";
this.limits = limits;
}
}
const fetchJson = (...args) => fetch(...args).then(async response => {
const {headers} = response;
const limits = {
remaining: headers.get("x-ratelimit-remaining"),
limit: headers.get("x-ratelimit-limit"),
reset: new Date(headers.get("x-ratelimit-reset") * 1000),
};
if (!response.ok) {
const message = response.statusText || response.status;
throw new GHRequestError(limits, message);
}
return {limits, payload: await response.json()};
});
const fetchGists = async (username, maxPages=10) => {
const gists = [];
let limits;
for (let page = 1; page <= maxPages; page++) {
const url = `https://api.github.com/users/${username}/gists?page=${page}`;
const {limits: lastLimits, payload: chunk} = await fetchJson(url);
limits = lastLimits;
if (chunk.length === 0) {
break;
}
gists.push(...chunk);
}
return {limits, gists};
};
const firstFile = gist =>
Object.keys(gist.files).length
? Object.values(gist.files)[0].filename : "";
const gistDescription = gist => `
<a href="${gist.html_url}">
${gist.description || (
Object.keys(gist.files).length
? firstFile(gist)
: "<em>no description</em>"
)
}
</a>
`;
const gistFiles = gist => `
<ul>
${Object.values(gist.files)
.map(e => `
<li><a href="${e.raw_url}">${e.filename}</a></li>
`)
.join("")}
</ul>
`;
const gistTableRow = gist => `
<tr>
<td>
${gistDescription(gist)}
</td>
<td>
${gistFiles(gist)}
</td>
<td>
${gist.created_at.slice(0, 10)}
</td>
<td>
${gist.updated_at.slice(0, 10)}
</td>
</tr>
`;
const gistsTable = gists =>
gists.length === 0 ? "<div>No gists found</div>" : `
<table>
<tbody>
<tr>
${headers.map(({name}) => `<th>${name}</th>`).join("")}
</tr>
${gists.map(gistTableRow).join("")}
</tbody>
</table>
`;
const listenToHeaderClick = header => {
header.addEventListener("click", ({target: {textContent}}) => {
let {key, ascending} = headers.find(e => e.name === textContent);
if (sortedBy === textContent) {
gists.reverse();
ascending = header.classList.contains("sorted-desc");
}
else {
sortedBy = textContent;
gists.sort((a, b) => key(a).localeCompare(key(b)));
if (!ascending) {
gists.reverse();
}
}
gistsToDOM(gists);
showSortedIcon(ascending);
});
};
const gistsToDOM = gists => {
document.querySelector(".gists").innerHTML = gistsTable(gists);
document.querySelectorAll(".gists th").forEach(listenToHeaderClick);
};
const limitsToDOM = limits => {
document.querySelector(".github-limits").innerHTML = `
<small>
<em>
${limits.remaining}/${limits.limit} API requests remaining.
Usage resets at ${limits.reset}.
</em>
</small>
`;
};
const showSortedIcon = ascending => {
document.querySelectorAll(".gists th").forEach(e => {
if (e.textContent === sortedBy) {
e.classList.add(ascending ? "sorted-asc" : "sorted-desc");
}
});
};
const handleGistsResponse = response => {
({limits, gists} = response);
limitsToDOM(limits);
gistsToDOM(gists);
showSortedIcon(headers.find(e => e.name === sortedBy).ascending);
};
const handleError = err => {
const gistsEl = document.querySelector(".gists");
gistsEl.innerHTML = err.message;
limitsToDOM(err.limits);
};
const searchGists = username => {
const submit = document.querySelector(".gist-search [type='submit']");
submit.disabled = true;
fetchGists(username)
.then(handleGistsResponse)
.catch(handleError)
.finally(() => submit.disabled = false);
};
const trySearchFromURLParam = () => {
const username = new URLSearchParams(window.location.search).get("username");
if (username) {
const unEl = document.querySelector(".github-username");
unEl.value = username;
searchGists(username);
}
};
const listenForSubmit = () => {
document.querySelector(".gist-search")
.addEventListener("submit", event => {
event.preventDefault();
const {value} = event.target.querySelector(".github-username");
searchGists(value);
});
};
let limits;
let gists;
let sortedBy = "Updated";
const headers = Object.freeze([
{
name: "Description",
key: e => e.description || firstFile(e),
ascending: true,
},
{
name: "Files",
key: firstFile,
ascending: true,
},
{
name: "Created",
key: e => e.created_at,
ascending: false,
},
{
name: "Updated",
key: e => e.updated_at,
ascending: false,
},
]);
trySearchFromURLParam();
listenForSubmit();
</script>
</body>
</html>