-
Notifications
You must be signed in to change notification settings - Fork 594
Expand file tree
/
Copy pathutterances.ts
More file actions
170 lines (152 loc) · 5.33 KB
/
utterances.ts
File metadata and controls
170 lines (152 loc) · 5.33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
import { pageAttributes as page } from './page-attributes';
import {
createIssue as createGitHubIssue,
Issue,
IssueComment,
loadCommentsPage,
loadIssueByNumber,
loadIssueByTerm,
loadUser,
PAGE_SIZE,
postComment,
setRepoContext
} from './github';
import { TimelineComponent } from './timeline-component';
import { NewCommentComponent } from './new-comment-component';
import { scheduleMeasure, startMeasuring } from './measure';
import { loadTheme } from './theme';
import { getRepoConfig } from './repo-config';
import { loadToken } from './oauth';
import { enableReactions } from './reactions';
import { PostReactionComponent } from './post-reaction-component';
setRepoContext(page);
function loadIssue(): Promise<Issue | null> {
if (page.issueNumber !== null) {
return loadIssueByNumber(page.issueNumber);
}
return loadIssueByTerm(page.issueTerm as string);
}
async function bootstrap() {
const main = document.createElement('main');
document.body.appendChild(main);
await loadToken();
// tslint:disable-next-line:prefer-const
let [issue, user] = await Promise.all([
loadIssue(),
loadUser(),
loadTheme(page.theme, page.origin)
]);
startMeasuring(page.origin);
const timeline = new TimelineComponent(user, issue);
const createIssueCallback = async (newIssue: Issue) => {
issue = newIssue;
timeline.setIssue(issue);
}
const postReactionComponent = new PostReactionComponent(user, issue, createIssueCallback);
main.appendChild(postReactionComponent.element);
main.appendChild(timeline.element);
if (issue && issue.comments > 0) {
renderComments(issue, timeline);
}
scheduleMeasure();
if (issue && issue.locked) {
return;
}
enableReactions(!!user);
const submit = async (markdown: string) => {
await assertOrigin();
if (!issue) {
const newIssue = await createGitHubIssue(
page.issueTerm as string,
page.url,
page.title,
page.description || '',
page.label
);
issue = await loadIssueByNumber(newIssue.number);
timeline.setIssue(issue);
postReactionComponent.setIssue(issue);
}
const comment = await postComment(issue.number, markdown);
timeline.insertComment(comment, true);
newCommentComponent.clear();
};
const newCommentComponent = new NewCommentComponent(user, submit);
timeline.element.appendChild(newCommentComponent.element);
}
bootstrap();
addEventListener('not-installed', function handleNotInstalled() {
removeEventListener('not-installed', handleNotInstalled);
document.querySelector('.timeline')!.insertAdjacentHTML('afterbegin', `
<div class="flash flash-error">
Error: utterances is not installed on <code>${page.owner}/${page.repo}</code>.
If you own this repo,
<a href="https://github.com/apps/utterances" target="_top"><strong>install the app</strong></a>.
Read more about this change in
<a href="https://github.com/utterance/utterances/pull/25" target="_top">the PR</a>.
</div>`);
scheduleMeasure();
});
async function renderComments(issue: Issue, timeline: TimelineComponent) {
const renderPage = (page: IssueComment[]) => {
for (const comment of page) {
timeline.insertComment(comment, false);
}
};
const pageCount = Math.ceil(issue.comments / PAGE_SIZE);
// always load the first page.
const pageLoads = [loadCommentsPage(issue.number, 1)];
// if there are multiple pages, load the last page.
if (pageCount > 1) {
pageLoads.push(loadCommentsPage(issue.number, pageCount));
}
// if the last page is small, load the penultimate page.
if (pageCount > 2 && issue.comments % PAGE_SIZE < 3) {
pageLoads.push(loadCommentsPage(issue.number, pageCount - 1));
}
// await all loads to reduce jank.
const pages = await Promise.all(pageLoads);
for (const page of pages) {
renderPage(page);
}
// enable loading hidden pages.
let hiddenPageCount = pageCount - pageLoads.length;
let nextHiddenPage = 2;
const renderLoader = (afterPage: IssueComment[]) => {
if (hiddenPageCount === 0) {
return;
}
const load = async () => {
loader.setBusy();
const page = await loadCommentsPage(issue.number, nextHiddenPage);
loader.remove();
renderPage(page);
hiddenPageCount--;
nextHiddenPage++;
renderLoader(page);
};
const afterComment = afterPage.pop()!;
const loader = timeline.insertPageLoader(afterComment, hiddenPageCount * PAGE_SIZE, load);
};
renderLoader(pages[0]);
}
export async function assertOrigin() {
const {origins} = await getRepoConfig();
const {origin, owner, repo} = page;
if (origins.indexOf(origin) !== -1) {
return;
}
document.querySelector('.timeline')!.lastElementChild!.insertAdjacentHTML('beforebegin', `
<div class="flash flash-error flash-not-installed">
Error: <code>${origin}</code> is not permitted to post to <code>${owner}/${repo}</code>.
Confirm this is the correct repo for this site's comments. If you own this repo,
<a href="https://github.com/${owner}/${repo}/edit/master/utterances.json" target="_top">
<strong>update the utterances.json</strong>
</a>
to include <code>${origin}</code> in the list of origins.<br/><br/>
Suggested configuration:<br/>
<pre><code>${JSON.stringify({origins: [origin]}, null, 2)}</code></pre>
</div>`);
scheduleMeasure();
throw new Error('Origin not permitted.');
}