Skip to content

Commit

Permalink
Merge pull request pomber#46 from pomber/oauth
Browse files Browse the repository at this point in the history
Add GitHub log in
  • Loading branch information
pomber authored Feb 7, 2019
2 parents 47e29bd + 2d9be64 commit c312400
Show file tree
Hide file tree
Showing 7 changed files with 245 additions and 66 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"private": true,
"dependencies": {
"diff": "^4.0.1",
"netlify-auth-providers": "^1.0.0-alpha5",
"prismjs": "^1.15.0",
"react": "^16.8.1",
"react-dom": "^16.8.1",
Expand Down
Binary file modified public/favicon.ico
100755 → 100644
Binary file not shown.
10 changes: 1 addition & 9 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,6 @@
height: 100%;
}

#message {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
}

footer {
position: fixed;
right: 10px;
Expand All @@ -67,7 +59,7 @@
</style>
</head>
<body>
<div id="root"><div id="message"></div></div>
<div id="root"></div>
<footer>
<a href="https://github.com/pomber/github-history">GitHub History</a
><br />by <a href="https://twitter.com/pomber">@pomber</a>
Expand Down
178 changes: 172 additions & 6 deletions src/app.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,177 @@
import React from "react";
import ReactDOM from "react-dom";
import React, { useState, useEffect } from "react";
import History from "./history";
import { getHistory, auth, isLoggedIn } from "./github";

function App({ commits, language }) {
return <History commits={commits} language={language} />;
export default function AppWrapper(props) {
if (props.repo) {
return <App {...props} />;
} else {
return (
<Center>
<Landing />
</Center>
);
}
}

export function render(commits, root, lang) {
ReactDOM.render(<App commits={commits} language={lang} />, root);
function Center({ children }) {
return (
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
height: "100%"
}}
>
{children}
</div>
);
}

function Landing() {
return (
<p>
URL should be something like
https://github-history.netlify.com/user/repo/commits/master/path/to/file.js
</p>
);
}

function App({ repo, sha, path, lang }) {
const fileName = path.split("/").pop();
useDocumentTitle(`GitHub History - ${fileName}`);

const { commits, loading, error } = useCommitsFetcher({
repo,
sha,
path,
lang
});

if (error) {
return <Error error={error} />;
}

if (loading) {
return <Loading repo={repo} sha={sha} path={path} />;
}

if (!commits.length) {
return <Error error={{ status: 404 }} />;
}

return <History commits={commits} language={lang} />;
}

function Error({ error }) {
if (error.status === 403) {
return (
<Center>
<p>
GitHub API rate limit exceeded for your IP (60 requests per hour).
</p>
<p>Sign in with GitHub for more:</p>
<GitHubButton onClick={login} />
</Center>
);
}

if (error.status === 404) {
return (
<Center>
<p>File not found.</p>
{!isLoggedIn() && (
<React.Fragment>
<p>Is it from a private repo? Sign in with GitHub:</p>
<GitHubButton onClick={login} />
</React.Fragment>
)}
</Center>
);
}

console.error(error);
return (
<Center>
<p>Unexpected error. Check the console.</p>
</Center>
);
}

function Loading({ repo, sha, path }) {
return (
<Center>
<p>
Loading <strong>{repo}</strong> <strong>{path} history...</strong>
</p>
</Center>
);
}

function GitHubButton({ onClick }) {
return (
<button
onClick={onClick}
style={{ fontWeight: 600, padding: "0.5em 0.7em", cursor: "pointer" }}
>
<div>
<svg
fill="currentColor"
preserveAspectRatio="xMidYMid meet"
height="1em"
width="1em"
viewBox="0 0 40 40"
style={{ verticalAlign: "middle", marginRight: "0.5rem" }}
>
<g>
<path d="m20 0c-11 0-20 9-20 20 0 8.8 5.7 16.3 13.7 19 1 0.2 1.3-0.5 1.3-1 0-0.5 0-2 0-3.7-5.5 1.2-6.7-2.4-6.7-2.4-0.9-2.3-2.2-2.9-2.2-2.9-1.9-1.2 0.1-1.2 0.1-1.2 2 0.1 3.1 2.1 3.1 2.1 1.7 3 4.6 2.1 5.8 1.6 0.2-1.3 0.7-2.2 1.3-2.7-4.5-0.5-9.2-2.2-9.2-9.8 0-2.2 0.8-4 2.1-5.4-0.2-0.5-0.9-2.6 0.2-5.3 0 0 1.7-0.5 5.5 2 1.6-0.4 3.3-0.6 5-0.6 1.7 0 3.4 0.2 5 0.7 3.8-2.6 5.5-2.1 5.5-2.1 1.1 2.8 0.4 4.8 0.2 5.3 1.3 1.4 2.1 3.2 2.1 5.4 0 7.6-4.7 9.3-9.2 9.8 0.7 0.6 1.4 1.9 1.4 3.7 0 2.7 0 4.9 0 5.5 0 0.6 0.3 1.2 1.3 1 8-2.7 13.7-10.2 13.7-19 0-11-9-20-20-20z" />
</g>
</svg>
Sign in with GitHub
</div>
</button>
);
}

function login() {
auth()
.then(data => {
window.location.reload(false);
})
.catch(console.error);
}

function useCommitsFetcher({ repo, sha, path, lang }) {
const [state, setState] = useState({
commits: null,
loading: true,
error: null
});

useEffect(() => {
getHistory(repo, sha, path, lang)
.then(commits => {
setState({
commits,
loading: false,
error: false
});
})
.catch(error => {
setState({
loading: false,
error
});
});
}, [repo, sha, path, lang]);

return state;
}

function useDocumentTitle(title) {
useEffect(() => {
document.title = title;
}, [title]);
}
65 changes: 61 additions & 4 deletions src/github.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
import { getLanguageDependencies } from "./language-detector";
import netlify from "netlify-auth-providers";
const TOKEN_KEY = "github-token";

function getHeaders() {
const token = window.localStorage.getItem(TOKEN_KEY);
return token ? { Authorization: `bearer ${token}` } : {};
}

export function isLoggedIn() {
return !!window.localStorage.getItem(TOKEN_KEY);
}

async function getContent(repo, sha, path) {
const contentResponse = await fetch(
`https://api.github.com/repos/${repo}/contents${path}?ref=${sha}`
`https://api.github.com/repos/${repo}/contents${path}?ref=${sha}`,
{ headers: getHeaders() }
);

if (!contentResponse.ok) {
Expand All @@ -11,15 +25,16 @@ async function getContent(repo, sha, path) {
return { content, url: contentJson.html_url };
}

export async function getHistory(repo, sha, path, top = 10) {
async function getCommits(repo, sha, path, top = 10) {
const commitsResponse = await fetch(
`https://api.github.com/repos/${repo}/commits?sha=${sha}&path=${path}`
`https://api.github.com/repos/${repo}/commits?sha=${sha}&path=${path}`,
{ headers: getHeaders() }
);
if (!commitsResponse.ok) {
throw commitsResponse;
}
const commitsJson = await commitsResponse.json();
// console.log(commitsJson);

const commits = commitsJson
.slice(0, top)
.map(commit => ({
Expand Down Expand Up @@ -48,3 +63,45 @@ export async function getHistory(repo, sha, path, top = 10) {

return commits;
}

export function getHistory(repo, sha, path, lang) {
return Promise.all([getCommits(repo, sha, path), loadLanguage(lang)]).then(
([commits]) => commits
);
}

export function auth() {
return new Promise((resolve, reject) => {
var authenticator = new netlify({
site_id: "ccf3a0e2-ac06-4f37-9b17-df1dd41fb1a6"
});
authenticator.authenticate({ provider: "github", scope: "repo" }, function(
err,
data
) {
if (err) {
reject(err);
}
window.localStorage.setItem(TOKEN_KEY, data.token);
resolve(data);
});
});
}

function loadLanguage(lang) {
if (["js", "css", "html"].includes(lang)) {
return Promise.resolve();
}

const deps = getLanguageDependencies(lang);

let depPromise = import("prismjs");

if (deps) {
depPromise = depPromise.then(() =>
Promise.all(deps.map(dep => import(`prismjs/components/prism-${dep}`)))
);
}

return depPromise.then(() => import(`prismjs/components/prism-${lang}`));
}
52 changes: 5 additions & 47 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,55 +1,13 @@
import { getHistory } from "./github";
import { getLanguage, getLanguageDependencies } from "./language-detector";
import { getLanguage } from "./language-detector";
import App from "./app";
import React from "react";
import ReactDOM from "react-dom";

const [repo, sha, path] = getParams();
const lang = getLanguage(path);
const root = document.getElementById("root");
const message = document.getElementById("message");

if (!repo) {
// show docs
message.innerHTML = `<p>URL should be something like https://github-history.netlify.com/user/repo/commits/master/path/to/file.js</p>`;
} else {
// show loading
message.innerHTML = `<p>Loading <strong>${repo}</strong> <strong>${path}</strong> history...</p>`;
document.title = `GitHub History - ${path.split("/").pop()}`;

Promise.all([
getHistory(repo, sha, path),
import("./app"),
loadLanguage(lang)
])
.then(([commits, app]) => {
app.render(commits, root, lang);
})
.catch(error => {
if (error.status === 403) {
message.innerHTML =
"<p>GitHub API rate limit exceeded for your IP (60 requests per hour).</p><p>I need to add authentication.</p>";
} else {
console.error(error);
message.innerHTML = `<p>Unexpected error. Check the console.</p>`;
}
});
}

function loadLanguage(lang) {
if (["js", "css", "html"].includes(lang)) {
return Promise.resolve();
}

const deps = getLanguageDependencies(lang);

let depPromise = import("prismjs");

if (deps) {
depPromise = depPromise.then(() =>
Promise.all(deps.map(dep => import(`prismjs/components/prism-${dep}`)))
);
}

return depPromise.then(() => import(`prismjs/components/prism-${lang}`));
}
ReactDOM.render(<App repo={repo} sha={sha} path={path} lang={lang} />, root);

function getParams() {
const [
Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6439,6 +6439,11 @@ neo-async@^2.5.0:
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.0.tgz#b9d15e4d71c6762908654b5183ed38b753340835"
integrity sha512-MFh0d/Wa7vkKO3Y3LlacqAEeHK0mckVqzDieUKTT+KGxi+zIpeVsFxymkIiRpbpDziHc290Xr9A1O4Om7otoRA==

netlify-auth-providers@^1.0.0-alpha5:
version "1.0.0-alpha5"
resolved "https://registry.yarnpkg.com/netlify-auth-providers/-/netlify-auth-providers-1.0.0-alpha5.tgz#f0ce642fe5534f04a1d539ca847c907dd20819c8"
integrity sha512-V4tqW60NEOYdd7QUWotB+XeMbw/kayi4Sbm67hSMWibXHG7xiRUp6+VEB8CmBt7/kb3HTw7+mQSwF7YR9hRaSQ==

nice-try@^1.0.4:
version "1.0.5"
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
Expand Down

0 comments on commit c312400

Please sign in to comment.