From 4ee2c2522fecaefcbe9267e5bf834c97e9437e12 Mon Sep 17 00:00:00 2001 From: Arunvel Sriram Date: Sun, 14 Jun 2020 09:35:32 +0530 Subject: [PATCH 1/9] Gitignore .idea --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 8df878e8..3e78d89b 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ chrome dist node_modules yarn-error.log +.idea From f7a6025db8f7d8984c922566319c9af8b90bf959 Mon Sep 17 00:00:00 2001 From: Arunvel Sriram Date: Sat, 13 Jun 2020 13:14:45 +0530 Subject: [PATCH 2/9] Use section for holding timeline and wrap it inside main --- src/timeline-component.ts | 2 +- src/utterances.ts | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/timeline-component.ts b/src/timeline-component.ts index 7b6efb08..bd4c1c7e 100644 --- a/src/timeline-component.ts +++ b/src/timeline-component.ts @@ -13,7 +13,7 @@ export class TimelineComponent { private user: User | null, private issue: Issue | null ) { - this.element = document.createElement('main'); + this.element = document.createElement('section'); this.element.classList.add('timeline'); this.element.innerHTML = `

diff --git a/src/utterances.ts b/src/utterances.ts index 0967b152..c76537ab 100644 --- a/src/utterances.ts +++ b/src/utterances.ts @@ -29,6 +29,9 @@ function loadIssue(): Promise { } 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([ @@ -40,7 +43,7 @@ async function bootstrap() { startMeasuring(page.origin); const timeline = new TimelineComponent(user, issue); - document.body.appendChild(timeline.element); + main.appendChild(timeline.element); if (issue && issue.comments > 0) { renderComments(issue, timeline); From 715fa808f5672ea58e2a4f910688a655bba984d9 Mon Sep 17 00:00:00 2001 From: Arunvel Sriram Date: Sat, 13 Jun 2020 17:47:55 +0530 Subject: [PATCH 3/9] Render reactions from existing issue --- src/post-reaction-component.ts | 115 ++++++++++++++++++++++++++++ src/stylesheets/post-reactions.scss | 19 +++++ src/stylesheets/utterances.scss | 1 + src/utterances.ts | 37 ++++++--- 4 files changed, 161 insertions(+), 11 deletions(-) create mode 100644 src/post-reaction-component.ts create mode 100644 src/stylesheets/post-reactions.scss diff --git a/src/post-reaction-component.ts b/src/post-reaction-component.ts new file mode 100644 index 00000000..75ee45c9 --- /dev/null +++ b/src/post-reaction-component.ts @@ -0,0 +1,115 @@ +import { Issue, ReactionID, Reactions, reactionTypes, toggleReaction } from './github'; +import { reactionEmoji, reactionNames } from './reactions'; + +class EmptyReactions implements Reactions { + '+1' = 0; + '-1' = 0; + confused = 0; + eyes = 0; + heart = 0; + hooray = 0; + laugh = 0; + rocket = 0; + // tslint:disable-next-line:variable-name + total_count = 0; + url = ''; +} + +export class PostReactionComponent { + public readonly element: HTMLElement; + private readonly countAnchor: HTMLAnchorElement; + private readonly reactionListContainer: HTMLFormElement; + private reactions: Reactions = new EmptyReactions(); + private reactionsCount: number = 0; + private issueURL: string = ''; + + constructor( + private issue: Issue | null, + private createIssue: () => Promise + ) { + this.element = document.createElement('section'); + this.element.classList.add('post-reactions'); + this.element.innerHTML = ` +
+ +
+
+
`; + this.countAnchor = this.element.querySelector('header a') as HTMLAnchorElement; + this.reactionListContainer = this.element.querySelector('.post-reaction-list') as HTMLFormElement; + this.setIssue(this.issue) + this.render(); + this.setupSubmitHandler(); + } + + public setIssue(issue: Issue | null) { + this.issue = issue; + if (issue) { + this.reactions = issue.reactions; + this.reactionsCount = issue.reactions.total_count; + this.issueURL = issue.html_url; + this.render(); + } + } + + private setupSubmitHandler() { + const handler = async (event: Event) => { + event.preventDefault(); + + const issueExists = !!this.issue; + if (!issueExists) { + await this.createIssue(); + } + + const button = event.target as HTMLButtonElement; + button.disabled = true; + const url = button.formAction; + const id = button.value as ReactionID; + const {deleted} = await toggleReaction(url, id); + const selector = `button[post-reaction][formaction="${url}"][value="${id}"],[reaction-count][reaction-url="${url}"]`; + const elements = Array.from(this.element.querySelectorAll(selector)); + const delta = deleted ? -1 : 1; + for (const element of elements) { + element.setAttribute( + 'reaction-count', + (parseInt(element.getAttribute('reaction-count')!, 10) + delta).toString()); + } + button.disabled = false; + } + + const buttons = this.element.querySelectorAll('button[post-reaction]'); + buttons.forEach(button => button.addEventListener('click', handler, true)) + } + + private getSubmitButtons(): string { + function buttonFor(url: string, reaction: ReactionID, disabled: boolean, count: number): string { + return ` + `; + } + + return reactionTypes + .map(id => buttonFor(this.reactions.url, id, false, this.reactions[id])) + .join('') + } + + private render() { + if (this.issueURL !== '') { + this.countAnchor.href = this.issueURL; + } else { + this.countAnchor.removeAttribute('href'); + } + this.countAnchor.textContent = `${this.reactionsCount} Reaction${this.reactionsCount === 1 ? '' : 's'}`; + this.reactionListContainer.innerHTML = this.getSubmitButtons(); + } +} diff --git a/src/stylesheets/post-reactions.scss b/src/stylesheets/post-reactions.scss new file mode 100644 index 00000000..e1bf6be8 --- /dev/null +++ b/src/stylesheets/post-reactions.scss @@ -0,0 +1,19 @@ +.post-reactions { + margin: $spacer-3 0; + padding: 0 $spacer-1; + display: flex; + flex-direction: column; + align-items: center; + + .post-reaction-list > .post-reaction-button { + font-weight: normal; + padding: $spacer-2 $spacer-3; + border-radius: 0 !important; + + &::after { + display: inline-block; + margin-left: 2px; + content: attr(reaction-count); + } + } +} diff --git a/src/stylesheets/utterances.scss b/src/stylesheets/utterances.scss index 6c339851..72427df9 100644 --- a/src/stylesheets/utterances.scss +++ b/src/stylesheets/utterances.scss @@ -8,6 +8,7 @@ @import "@primer/css/forms/form-control"; @import "@primer/css/popover/index"; @import "./util"; +@import "./post-reactions"; @import "./timeline"; @import "./timeline-comment"; @import "./permalink-code"; diff --git a/src/utterances.ts b/src/utterances.ts index c76537ab..4087eb49 100644 --- a/src/utterances.ts +++ b/src/utterances.ts @@ -1,23 +1,24 @@ import { pageAttributes as page } from './page-attributes'; import { + createIssue as createGitHubIssue, Issue, - setRepoContext, - loadIssueByTerm, - loadIssueByNumber, + IssueComment, loadCommentsPage, + loadIssueByNumber, + loadIssueByTerm, loadUser, - postComment, - createIssue, PAGE_SIZE, - IssueComment + postComment, + setRepoContext } from './github'; import { TimelineComponent } from './timeline-component'; import { NewCommentComponent } from './new-comment-component'; -import { startMeasuring, scheduleMeasure } from './measure'; +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); @@ -43,6 +44,19 @@ async function bootstrap() { startMeasuring(page.origin); const timeline = new TimelineComponent(user, issue); + const createIssue = async () => { + issue = await createGitHubIssue( + page.issueTerm as string, + page.url, + page.title, + page.description || '', + page.label + ) + timeline.setIssue(issue); + postReactionComponent.setIssue(issue); + } + const postReactionComponent = new PostReactionComponent(issue, createIssue); + main.appendChild(postReactionComponent.element); main.appendChild(timeline.element); if (issue && issue.comments > 0) { @@ -60,7 +74,7 @@ async function bootstrap() { const submit = async (markdown: string) => { await assertOrigin(); if (!issue) { - issue = await createIssue( + issue = await createGitHubIssue( page.issueTerm as string, page.url, page.title, @@ -68,6 +82,7 @@ async function bootstrap() { page.label ); timeline.setIssue(issue); + postReactionComponent.setIssue(issue) } const comment = await postComment(issue.number, markdown); timeline.insertComment(comment, true); @@ -139,8 +154,8 @@ async function renderComments(issue: Issue, timeline: TimelineComponent) { } export async function assertOrigin() { - const { origins } = await getRepoConfig(); - const { origin, owner, repo } = page; + const {origins} = await getRepoConfig(); + const {origin, owner, repo} = page; if (origins.indexOf(origin) !== -1) { return; } @@ -154,7 +169,7 @@ export async function assertOrigin() { to include ${origin} in the list of origins.

Suggested configuration:
-
${JSON.stringify({ origins: [origin] }, null, 2)}
+
${JSON.stringify({origins: [origin]}, null, 2)}
`); scheduleMeasure(); throw new Error('Origin not permitted.'); From f28dc779f8303fef92db5e562d0c64027571eea2 Mon Sep 17 00:00:00 2001 From: Arunvel Sriram Date: Sun, 14 Jun 2020 23:44:25 +0530 Subject: [PATCH 4/9] Make reactions work for new issue created through comment - bug exists in new issue created through reaction buttons --- src/post-reaction-component.ts | 18 ++---------------- src/reactions.ts | 16 +++++++++++++++- src/utterances.ts | 13 +++---------- 3 files changed, 20 insertions(+), 27 deletions(-) diff --git a/src/post-reaction-component.ts b/src/post-reaction-component.ts index 75ee45c9..cbadc7ee 100644 --- a/src/post-reaction-component.ts +++ b/src/post-reaction-component.ts @@ -1,19 +1,5 @@ import { Issue, ReactionID, Reactions, reactionTypes, toggleReaction } from './github'; -import { reactionEmoji, reactionNames } from './reactions'; - -class EmptyReactions implements Reactions { - '+1' = 0; - '-1' = 0; - confused = 0; - eyes = 0; - heart = 0; - hooray = 0; - laugh = 0; - rocket = 0; - // tslint:disable-next-line:variable-name - total_count = 0; - url = ''; -} +import { EmptyReactions, reactionEmoji, reactionNames } from './reactions'; export class PostReactionComponent { public readonly element: HTMLElement; @@ -39,7 +25,6 @@ export class PostReactionComponent { this.reactionListContainer = this.element.querySelector('.post-reaction-list') as HTMLFormElement; this.setIssue(this.issue) this.render(); - this.setupSubmitHandler(); } public setIssue(issue: Issue | null) { @@ -111,5 +96,6 @@ export class PostReactionComponent { } this.countAnchor.textContent = `${this.reactionsCount} Reaction${this.reactionsCount === 1 ? '' : 's'}`; this.reactionListContainer.innerHTML = this.getSubmitButtons(); + this.setupSubmitHandler(); } } diff --git a/src/reactions.ts b/src/reactions.ts index c9305696..ae2c2be9 100644 --- a/src/reactions.ts +++ b/src/reactions.ts @@ -1,4 +1,4 @@ -import { toggleReaction, ReactionID, reactionTypes } from './github'; +import { ReactionID, Reactions, reactionTypes, toggleReaction } from './github'; import { getLoginUrl } from './oauth'; import { pageAttributes } from './page-attributes'; import { scheduleMeasure } from './measure'; @@ -25,6 +25,20 @@ export const reactionEmoji: Record = { 'eyes': '👀' }; +export class EmptyReactions implements Reactions { + '+1' = 0; + '-1' = 0; + confused = 0; + eyes = 0; + heart = 0; + hooray = 0; + laugh = 0; + rocket = 0; + // tslint:disable-next-line:variable-name + total_count = 0; + url = ''; +} + export function getReactionHtml(url: string, reaction: ReactionID, disabled: boolean, count: number) { return ` `; } + const issueLocked = this.issue ? this.issue.locked : false; return reactionTypes - .map(id => buttonFor(this.reactions.url, id, false, this.reactions[id])) + .map(id => buttonFor(this.reactions.url, id, !this.user || issueLocked, this.reactions[id])) .join('') } diff --git a/src/utterances.ts b/src/utterances.ts index fdc862cf..79628b50 100644 --- a/src/utterances.ts +++ b/src/utterances.ts @@ -48,7 +48,7 @@ async function bootstrap() { issue = newIssue; timeline.setIssue(issue); } - const postReactionComponent = new PostReactionComponent(issue, createIssueCallback); + const postReactionComponent = new PostReactionComponent(user, issue, createIssueCallback); main.appendChild(postReactionComponent.element); main.appendChild(timeline.element);