mirror of
https://github.com/utterance/utterances.git
synced 2024-10-27 00:13:52 +03:00
oauth update, author association badges, bug-fixes
This commit is contained in:
parent
f37b11407c
commit
dfdff63ad5
4583
package-lock.json
generated
Normal file
4583
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -77,7 +77,7 @@
|
||||
background-color: $bg-blue-light;
|
||||
}
|
||||
|
||||
&.repo-owner .comment-header > :last-child::after {
|
||||
.author-association-badge {
|
||||
float: right;
|
||||
margin-top: -1px;
|
||||
padding: 2px 5px;
|
||||
@ -85,7 +85,6 @@
|
||||
border-radius: $border-radius;
|
||||
font-size: $font-size-small;
|
||||
font-weight: $font-weight-semibold;
|
||||
content: 'Owner';
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>utterances</title>
|
||||
<title>login | utterances</title>
|
||||
<script>
|
||||
window.opener.oautherized(window.location.search.substring(1));
|
||||
window.close();
|
||||
opener.notifyAuthorized();
|
||||
close();
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
|
@ -1,8 +1,13 @@
|
||||
import { IssueComment } from './github';
|
||||
import { timeAgo } from './time-ago';
|
||||
import { isOwner } from './page-attributes';
|
||||
|
||||
const avatarArgs = '?v=3&s=88';
|
||||
const displayAssociations: { [association: string]: string; } = {
|
||||
COLLABORATOR: 'Collaborator',
|
||||
CONTRIBUTOR: 'Contributor',
|
||||
MEMBER: 'Member',
|
||||
OWNER: 'Owner'
|
||||
};
|
||||
|
||||
export class CommentComponent {
|
||||
public readonly element: HTMLElement;
|
||||
@ -11,15 +16,13 @@ export class CommentComponent {
|
||||
public comment: IssueComment,
|
||||
private currentUser: string | null
|
||||
) {
|
||||
const { user, html_url, created_at, body_html } = comment;
|
||||
const { user, html_url, created_at, body_html, author_association } = comment;
|
||||
this.element = document.createElement('article');
|
||||
this.element.classList.add('timeline-comment');
|
||||
if (user.login === currentUser) {
|
||||
this.element.classList.add('current-user');
|
||||
}
|
||||
if (isOwner(user.login)) {
|
||||
this.element.classList.add('repo-owner');
|
||||
}
|
||||
const association = displayAssociations[author_association];
|
||||
this.element.innerHTML = `
|
||||
<a class="avatar" href="${user.html_url}" target="_blank">
|
||||
<img alt="@${user.login}" height="44" width="44"
|
||||
@ -30,6 +33,7 @@ export class CommentComponent {
|
||||
<a class="text-link" href="${user.html_url}" target="_blank"><strong>${user.login}</strong></a>
|
||||
commented
|
||||
<a class="text-link" href="${html_url}" target="_blank">${timeAgo(Date.now(), new Date(created_at))}</a>
|
||||
${association ? `<span class="author-association-badge">${association}</span>` : ''}
|
||||
</header>
|
||||
<div class="markdown-body">
|
||||
${body_html}
|
||||
@ -49,11 +53,6 @@ export class CommentComponent {
|
||||
} else {
|
||||
this.element.classList.remove('current-user');
|
||||
}
|
||||
if (isOwner(user.login)) {
|
||||
this.element.classList.add('repo-owner');
|
||||
} else {
|
||||
this.element.classList.remove('repo-owner');
|
||||
}
|
||||
|
||||
const avatarAnchor = this.element.firstElementChild as HTMLAnchorElement;
|
||||
const avatarImg = avatarAnchor.firstElementChild as HTMLImageElement;
|
||||
@ -86,20 +85,13 @@ export class CommentComponent {
|
||||
if (this.currentUser === currentUser) {
|
||||
return;
|
||||
}
|
||||
|
||||
const commentDiv = this.element.firstElementChild as HTMLDivElement;
|
||||
if (this.comment.user.login === this.currentUser) {
|
||||
commentDiv.classList.add('current-user');
|
||||
} else {
|
||||
commentDiv.classList.remove('current-user');
|
||||
}
|
||||
if (isOwner(this.comment.user.login)) {
|
||||
this.element.classList.add('repo-owner');
|
||||
} else {
|
||||
this.element.classList.remove('repo-owner');
|
||||
}
|
||||
|
||||
this.currentUser = currentUser;
|
||||
|
||||
if (this.comment.user.login === this.currentUser) {
|
||||
this.element.classList.add('current-user');
|
||||
} else {
|
||||
this.element.classList.remove('current-user');
|
||||
}
|
||||
}
|
||||
|
||||
private retargetLinks() {
|
||||
|
@ -1,13 +1,11 @@
|
||||
import { token } from './oauth';
|
||||
import { decodeBase64UTF8 } from './encoding';
|
||||
import { UTTERANCES_API } from './utterances-api';
|
||||
|
||||
const GITHUB_API = 'https://api.github.com/';
|
||||
const GITHUB_ENCODING__HTML_JSON = 'application/vnd.github.VERSION.html+json';
|
||||
const GITHUB_ENCODING__HTML = 'application/vnd.github.VERSION.html';
|
||||
const GITHUB_ENCODING__REACTIONS_PREVIEW = 'application/vnd.github.squirrel-girl-preview';
|
||||
// const UTTERANCES_API = 'https://utterances-oauth.herokuapp.com';
|
||||
const UTTERANCES_API = 'https://utterances-oauth.azurewebsites.net';
|
||||
|
||||
const PAGE_SIZE = 100;
|
||||
|
||||
let owner: string;
|
||||
@ -184,6 +182,7 @@ export function createIssue(issueTerm: string, documentUrl: string, title: strin
|
||||
})
|
||||
});
|
||||
request.headers.set('Accept', GITHUB_ENCODING__REACTIONS_PREVIEW);
|
||||
request.headers.set('Authorization', `token ${token.value}`);
|
||||
return fetch(request).then<Issue>(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error('Error creating comments container issue');
|
||||
@ -231,6 +230,15 @@ export interface User {
|
||||
type: string;
|
||||
}
|
||||
|
||||
export type CommentAuthorAssociation =
|
||||
'COLLABORATOR'
|
||||
| 'CONTRIBUTOR'
|
||||
| 'FIRST_TIMER'
|
||||
| 'FIRST_TIME_CONTRIBUTOR'
|
||||
| 'MEMBER'
|
||||
| 'NONE'
|
||||
| 'OWNER';
|
||||
|
||||
export interface Issue {
|
||||
url: string;
|
||||
repository_url: string;
|
||||
@ -272,6 +280,7 @@ export interface Issue {
|
||||
hooray: number;
|
||||
url: string;
|
||||
};
|
||||
author_association: CommentAuthorAssociation;
|
||||
}
|
||||
|
||||
interface FileContentsResponse {
|
||||
@ -296,6 +305,7 @@ export interface IssueComment {
|
||||
user: User;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
author_association: CommentAuthorAssociation;
|
||||
}
|
||||
|
||||
export interface CommentsPage {
|
||||
|
@ -49,7 +49,6 @@
|
||||
repo="utterance/utterances"
|
||||
branch="gh-pages"
|
||||
issue-term="homepage"
|
||||
owners="jdanyow"
|
||||
async>
|
||||
</script>
|
||||
|
||||
|
74
src/oauth.ts
74
src/oauth.ts
@ -1,13 +1,11 @@
|
||||
import { param, deparam } from './deparam';
|
||||
import { UTTERANCES_API } from './utterances-api';
|
||||
import { param } from './deparam';
|
||||
|
||||
const authorizeUri = 'https://github.com/login/oauth/authorize';
|
||||
// const tokenUri = 'https://utterances-oauth.herokuapp.com/access-token';
|
||||
const tokenUri = 'https://utterances-oauth.azurewebsites.net/access-token';
|
||||
const authorizeUrl = `${UTTERANCES_API}/authorize`;
|
||||
const tokenUrl = `${UTTERANCES_API}/token`;
|
||||
// tslint:disable-next-line:variable-name
|
||||
const redirect_uri = 'https://utteranc.es/authorized.html';
|
||||
// tslint:disable-next-line:variable-name
|
||||
const client_id = '1a560753410b181458de';
|
||||
const scopes = 'public_repo';
|
||||
const redirect_uri = `${location.origin}/authorized.html`;
|
||||
const scope = 'public_repo';
|
||||
|
||||
class Token {
|
||||
private readonly storageKey = 'OAUTH_TOKEN';
|
||||
@ -38,55 +36,15 @@ class Token {
|
||||
|
||||
export const token = new Token();
|
||||
|
||||
interface AuthorizeResponse {
|
||||
code: string;
|
||||
state: string;
|
||||
}
|
||||
|
||||
function popup(url: string) {
|
||||
let resolve: (response: AuthorizeResponse) => void;
|
||||
(window as any).oautherized = (query: string) => {
|
||||
(window as any).oautherized = null;
|
||||
resolve(deparam(query) as any);
|
||||
};
|
||||
const promise = new Promise<AuthorizeResponse>(r => resolve = r);
|
||||
window.open(url);
|
||||
return promise;
|
||||
}
|
||||
|
||||
function requestAuthorizationCode() {
|
||||
const args = {
|
||||
client_id,
|
||||
redirect_uri,
|
||||
scope: scopes,
|
||||
state: Math.floor(Math.random() * 100000).toString()
|
||||
};
|
||||
const url = `${authorizeUri}?${param(args)}`;
|
||||
return popup(url)
|
||||
.then(result => {
|
||||
if (!(result.code && result.state)) {
|
||||
throw new Error('Redirect did not include code and state parameters.');
|
||||
}
|
||||
if (result.state !== args.state) {
|
||||
throw new Error('State mismatch.');
|
||||
}
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
function requestAccessToken({ code, state }: AuthorizeResponse) {
|
||||
const args = { code, state };
|
||||
const url = `${tokenUri}?${param(args)}`;
|
||||
return fetch(url)
|
||||
.then<{ access_token: string; }>(response => response.json());
|
||||
}
|
||||
|
||||
export function login() {
|
||||
return requestAuthorizationCode()
|
||||
.then(response => requestAccessToken(response))
|
||||
.then(({ access_token }) => token.value = access_token)
|
||||
.catch(reason => {
|
||||
token.value = null;
|
||||
throw reason;
|
||||
});
|
||||
window.open(`${authorizeUrl}?${param({ scope, redirect_uri })}`);
|
||||
return new Promise(resolve => (window as any).notifyAuthorized = resolve)
|
||||
.then(() => fetch(tokenUrl, { mode: 'cors', credentials: 'include' }))
|
||||
.then(response => {
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
}
|
||||
return response.text().then(text => Promise.reject(`Error retrieving token:\n${text}`));
|
||||
})
|
||||
.then(t => { token.value = t; }, reason => { token.value = null; throw reason; });
|
||||
}
|
||||
|
@ -40,7 +40,6 @@ function readPageAttributes() {
|
||||
|
||||
return {
|
||||
owner: matches[1],
|
||||
otherOwners: params.owners === undefined ? [] : params.owners.split(','),
|
||||
repo: matches[2],
|
||||
branch: 'branch' in params ? params.branch : 'master',
|
||||
configPath: 'config-path' in params ? params['config-path'] : 'utterances.json',
|
||||
@ -54,10 +53,3 @@ function readPageAttributes() {
|
||||
}
|
||||
|
||||
export const pageAttributes = readPageAttributes();
|
||||
|
||||
export function isOwner(login: string) {
|
||||
const { owner, otherOwners } = pageAttributes;
|
||||
const ignoreCase = { sensitivity: 'base' };
|
||||
return login.localeCompare(owner, undefined, ignoreCase) === 0
|
||||
|| otherOwners.find && otherOwners.find(o => login.localeCompare(o, undefined, ignoreCase) === 0);
|
||||
}
|
||||
|
2
src/utterances-api.ts
Normal file
2
src/utterances-api.ts
Normal file
@ -0,0 +1,2 @@
|
||||
// export const UTTERANCES_API = 'http://localhost:5000';
|
||||
export const UTTERANCES_API = 'https://utterances-oauth.azurewebsites.net';
|
Loading…
Reference in New Issue
Block a user