diff --git a/README.md b/README.md index e1e6e1fd..f1917bff 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,20 @@ -# lemmy-ui - -The official web app for [Lemmy](https://github.com/LemmyNet/lemmy), written in inferno. - -Based off of MrFoxPro's [inferno-isomorphic-template](https://github.com/MrFoxPro/inferno-isomorphic-template). - -## Configuration - -The following environment variables can be used to configure lemmy-ui: - -`ENV_VAR` | type | default | description ---- | --- | --- | --- -`LEMMY_UI_HOST` | `string` | `0.0.0.0:1234` | The IP / port that the lemmy-ui isomorphic node server is hosted at. -`LEMMY_UI_LEMMY_INTERNAL_HOST` | `string` | `0.0.0.0:8536` | The internal IP / port that lemmy is hosted at. Often `lemmy:8536` if using docker. -`LEMMY_UI_LEMMY_EXTERNAL_HOST` | `string` | `0.0.0.0:8536` | The external IP / port that lemmy is hosted at. Often `DOMAIN.TLD`. -`LEMMY_UI_LEMMY_WS_HOST` | `string` | `0.0.0.0:8536` | An alternate location for lemmy's websocket address. Not usually necessary. -`LEMMY_UI_HTTPS` | `bool` | `false` | Whether to use https. -`LEMMY_UI_EXTRA_THEMES_FOLDER` | `string` | `./extra_themes` | A location for additional lemmy css themes. -`LEMMY_UI_DEBUG` | `bool` | `false` | Loads the [Eruda](https://github.com/liriliri/eruda) debugging utility. -`LEMMY_UI_DISABLE_CSP` | `bool` | `false` | Disables CSP security headers -`LEMMY_UI_CUSTOM_HTML_HEADER` | `string` | | Injects a custom script into ``. +# Lemmy-UI + +The official web app for [Lemmy](https://github.com/LemmyNet/lemmy), written in inferno. + +Based off of MrFoxPro's [inferno-isomorphic-template](https://github.com/MrFoxPro/inferno-isomorphic-template). + +## Configuration + +The following environment variables can be used to configure lemmy-ui: + +| `ENV_VAR` | type | default | description | +| ------------------------------ | -------- | ---------------- | ----------------------------------------------------------------------------------- | +| `LEMMY_UI_HOST` | `string` | `0.0.0.0:1234` | The IP / port that the lemmy-ui isomorphic node server is hosted at. | +| `LEMMY_UI_LEMMY_INTERNAL_HOST` | `string` | `0.0.0.0:8536` | The internal IP / port that lemmy is hosted at. Often `lemmy:8536` if using docker. | +| `LEMMY_UI_LEMMY_EXTERNAL_HOST` | `string` | `0.0.0.0:8536` | The external IP / port that lemmy is hosted at. Often `DOMAIN.TLD`. | +| `LEMMY_UI_HTTPS` | `bool` | `false` | Whether to use https. | +| `LEMMY_UI_EXTRA_THEMES_FOLDER` | `string` | `./extra_themes` | A location for additional lemmy css themes. | +| `LEMMY_UI_DEBUG` | `bool` | `false` | Loads the [Eruda](https://github.com/liriliri/eruda) debugging utility. | +| `LEMMY_UI_DISABLE_CSP` | `bool` | `false` | Disables CSP security headers | +| `LEMMY_UI_CUSTOM_HTML_HEADER` | `string` | | Injects a custom script into ``. | diff --git a/src/shared/components/comment/comment-form.tsx b/src/shared/components/comment/comment-form.tsx index b76492a9..f2b5f931 100644 --- a/src/shared/components/comment/comment-form.tsx +++ b/src/shared/components/comment/comment-form.tsx @@ -14,6 +14,7 @@ interface CommentFormProps { * Can either be the parent, or the editable comment. The right side is a postId. */ node: CommentNodeI | number; + finished?: boolean; edit?: boolean; disabled?: boolean; focus?: boolean; @@ -24,25 +25,11 @@ interface CommentFormProps { onEditComment(form: EditComment): void; } -interface CommentFormState { - buttonTitle: string; -} - -export class CommentForm extends Component { - state: CommentFormState = { - buttonTitle: - typeof this.props.node === "number" - ? capitalizeFirstLetter(i18n.t("post")) - : this.props.edit - ? capitalizeFirstLetter(i18n.t("save")) - : capitalizeFirstLetter(i18n.t("reply")), - }; - +export class CommentForm extends Component { constructor(props: any, context: any) { super(props, context); this.handleCommentSubmit = this.handleCommentSubmit.bind(this); - this.handleReplyCancel = this.handleReplyCancel.bind(this); } render() { @@ -59,12 +46,13 @@ export class CommentForm extends Component { { ); } + get buttonTitle(): string { + return typeof this.props.node === "number" + ? capitalizeFirstLetter(i18n.t("post")) + : this.props.edit + ? capitalizeFirstLetter(i18n.t("save")) + : capitalizeFirstLetter(i18n.t("reply")); + } + handleCommentSubmit(content: string, form_id: string, language_id?: number) { let node = this.props.node; @@ -100,6 +96,7 @@ export class CommentForm extends Component { if (this.props.edit) { let comment_id = node.comment_view.comment.id; this.props.onEditComment({ + content, comment_id, form_id, language_id, @@ -119,8 +116,4 @@ export class CommentForm extends Component { } } } - - handleReplyCancel() { - this.props.onReplyCancel?.(); - } } diff --git a/src/shared/components/comment/comment-node.tsx b/src/shared/components/comment/comment-node.tsx index 008cd537..1f2bf49e 100644 --- a/src/shared/components/comment/comment-node.tsx +++ b/src/shared/components/comment/comment-node.tsx @@ -121,6 +121,7 @@ interface CommentNodeProps { allLanguages: Language[]; siteLanguages: number[]; hideImages?: boolean; + finished: Map; onSaveComment(form: SaveComment): void; onCommentReplyRead(form: MarkCommentReplyAsRead): void; onPersonMentionRead(form: MarkPersonMentionAsRead): void; @@ -197,6 +198,22 @@ export class CommentNode extends Component { ): void { if (this.props != nextProps) { this.setState({ + showReply: false, + showEdit: false, + showRemoveDialog: false, + showBanDialog: false, + removeData: false, + banType: BanType.Community, + showPurgeDialog: false, + purgeType: PurgeType.Person, + collapsed: false, + viewSource: false, + showAdvanced: false, + showConfirmTransferSite: false, + showConfirmTransferCommunity: false, + showConfirmAppointAsMod: false, + showConfirmAppointAsAdmin: false, + showReportDialog: false, createOrEditCommentLoading: false, upvoteLoading: false, downvoteLoading: false, @@ -389,6 +406,9 @@ export class CommentNode extends Component { edit onReplyCancel={this.handleReplyCancel} disabled={this.props.locked} + finished={this.props.finished.get( + this.props.node.comment_view.comment.id + )} focus allLanguages={this.props.allLanguages} siteLanguages={this.props.siteLanguages} @@ -1130,6 +1150,9 @@ export class CommentNode extends Component { node={node} onReplyCancel={this.handleReplyCancel} disabled={this.props.locked} + finished={this.props.finished.get( + this.props.node.comment_view.comment.id + )} focus allLanguages={this.props.allLanguages} siteLanguages={this.props.siteLanguages} @@ -1148,6 +1171,7 @@ export class CommentNode extends Component { allLanguages={this.props.allLanguages} siteLanguages={this.props.siteLanguages} hideImages={this.props.hideImages} + finished={this.props.finished} onCommentReplyRead={this.props.onCommentReplyRead} onPersonMentionRead={this.props.onPersonMentionRead} onCreateComment={this.props.onCreateComment} @@ -1457,7 +1481,7 @@ export class CommentNode extends Component { read: !cv.person_mention.read, auth: myAuthRequired(), }); - } else if (this.isCommentReplyType(cv)) { + } else if (i.isCommentReplyType(cv)) { i.props.onCommentReplyRead({ comment_reply_id: cv.comment_reply.id, read: !cv.comment_reply.read, diff --git a/src/shared/components/comment/comment-nodes.tsx b/src/shared/components/comment/comment-nodes.tsx index aadd3815..c1ad3097 100644 --- a/src/shared/components/comment/comment-nodes.tsx +++ b/src/shared/components/comment/comment-nodes.tsx @@ -5,6 +5,7 @@ import { BanFromCommunity, BanPerson, BlockPerson, + CommentId, CommunityModeratorView, CreateComment, CreateCommentLike, @@ -43,6 +44,7 @@ interface CommentNodesProps { allLanguages: Language[]; siteLanguages: number[]; hideImages?: boolean; + finished: Map; onSaveComment(form: SaveComment): void; onCommentReplyRead(form: MarkCommentReplyAsRead): void; onPersonMentionRead(form: MarkPersonMentionAsRead): void; @@ -94,6 +96,7 @@ export class CommentNodes extends Component { hideImages={this.props.hideImages} onCommentReplyRead={this.props.onCommentReplyRead} onPersonMentionRead={this.props.onPersonMentionRead} + finished={this.props.finished} onCreateComment={this.props.onCreateComment} onEditComment={this.props.onEditComment} onCommentVote={this.props.onCommentVote} diff --git a/src/shared/components/comment/comment-report.tsx b/src/shared/components/comment/comment-report.tsx index d88659e7..f6343759 100644 --- a/src/shared/components/comment/comment-report.tsx +++ b/src/shared/components/comment/comment-report.tsx @@ -1,4 +1,4 @@ -import { Component, linkEvent } from "inferno"; +import { Component, InfernoNode, linkEvent } from "inferno"; import { T } from "inferno-i18next-dess"; import { CommentReportView, @@ -32,6 +32,14 @@ export class CommentReport extends Component< super(props, context); } + componentWillReceiveProps( + nextProps: Readonly<{ children?: InfernoNode } & CommentReportProps> + ): void { + if (this.props != nextProps) { + this.setState({ loading: false }); + } + } + render() { let r = this.props.report; let comment = r.comment; @@ -73,6 +81,7 @@ export class CommentReport extends Component< siteLanguages={[]} hideImages // All of these are unused, since its viewonly + finished={new Map()} onSaveComment={() => {}} onBlockPerson={() => {}} onDeleteComment={() => {}} diff --git a/src/shared/components/common/markdown-textarea.tsx b/src/shared/components/common/markdown-textarea.tsx index fb5148a2..32cf5deb 100644 --- a/src/shared/components/common/markdown-textarea.tsx +++ b/src/shared/components/common/markdown-textarea.tsx @@ -36,6 +36,7 @@ interface MarkdownTextAreaProps { replyType?: boolean; focus?: boolean; disabled?: boolean; + finished?: boolean; showLanguage?: boolean; hideNavigationWarnings?: boolean; onContentChange?(val: string): void; @@ -113,12 +114,7 @@ export class MarkdownTextArea extends Component< } componentWillReceiveProps(nextProps: MarkdownTextAreaProps) { - if ( - nextProps != this.props && - // Don't trigger this on an initial content change (IE the form field version) - // This should only trigger in an - this.props.initialContent == nextProps.initialContent - ) { + if (nextProps.finished) { this.setState({ previewMode: false, imageUploadStatus: undefined, diff --git a/src/shared/components/community/community.tsx b/src/shared/components/community/community.tsx index e19a8c85..3b3ae864 100644 --- a/src/shared/components/community/community.tsx +++ b/src/shared/components/community/community.tsx @@ -10,6 +10,7 @@ import { BanPersonResponse, BlockCommunity, BlockPerson, + CommentId, CommentReplyResponse, CommentResponse, CommunityResponse, @@ -73,6 +74,7 @@ import { enableDownvotes, enableNsfw, fetchLimit, + getCommentParentId, getDataTypeString, getPageFromString, getQueryParams, @@ -108,6 +110,7 @@ interface State { commentsRes: RequestState; siteRes: GetSiteResponse; showSidebarMobile: boolean; + finished: Map; } interface CommunityProps { @@ -147,6 +150,7 @@ export class Community extends Component< commentsRes: { state: "empty" }, siteRes: this.isoData.site_res, showSidebarMobile: false, + finished: new Map(), }; constructor(props: RouteComponentProps<{ name: string }>, context: any) { @@ -445,6 +449,7 @@ export class Community extends Component< { - if ( - s.commentsRes.state == "success" && - createCommentRes.state == "success" - ) { - s.commentsRes.data.comments.unshift(createCommentRes.data.comment_view); - } - return s; - }); + this.createAndUpdateComments(createCommentRes); } async handleEditComment(form: EditComment) { @@ -924,6 +920,22 @@ export class Community extends Component< res.data.comment_view, s.commentsRes.data.comments ); + s.finished.set(res.data.comment_view.comment.id, true); + } + return s; + }); + } + + createAndUpdateComments(res: RequestState) { + this.setState(s => { + if (s.commentsRes.state == "success" && res.state == "success") { + s.commentsRes.data.comments.unshift(res.data.comment_view); + + // Set finished for the parent + s.finished.set( + getCommentParentId(res.data.comment_view.comment) ?? 0, + true + ); } return s; }); diff --git a/src/shared/components/home/home.tsx b/src/shared/components/home/home.tsx index cea29c54..d658cf3e 100644 --- a/src/shared/components/home/home.tsx +++ b/src/shared/components/home/home.tsx @@ -10,6 +10,7 @@ import { BanPerson, BanPersonResponse, BlockPerson, + CommentId, CommentReplyResponse, CommentResponse, CreateComment, @@ -67,6 +68,7 @@ import { enableDownvotes, enableNsfw, fetchLimit, + getCommentParentId, getDataTypeString, getPageFromString, getQueryParams, @@ -108,6 +110,7 @@ interface HomeState { subscribedCollapsed: boolean; tagline?: string; siteRes: GetSiteResponse; + finished: Map; } interface HomeProps { @@ -186,6 +189,7 @@ export class Home extends Component { showTrendingMobile: false, showSidebarMobile: false, subscribedCollapsed: false, + finished: new Map(), }; constructor(props: any, context: any) { @@ -646,6 +650,7 @@ export class Home extends Component { { HttpService.client.createComment(form) ); - this.setState(s => { - if ( - s.commentsRes.state == "success" && - createCommentRes.state == "success" - ) { - s.commentsRes.data.comments.unshift(createCommentRes.data.comment_view); - } - return s; - }); + this.createAndUpdateComments(createCommentRes); } async handleEditComment(form: EditComment) { @@ -1057,6 +1054,22 @@ export class Home extends Component { res.data.comment_view, s.commentsRes.data.comments ); + s.finished.set(res.data.comment_view.comment.id, true); + } + return s; + }); + } + + createAndUpdateComments(res: RequestState) { + this.setState(s => { + if (s.commentsRes.state == "success" && res.state == "success") { + s.commentsRes.data.comments.unshift(res.data.comment_view); + + // Set finished for the parent + s.finished.set( + getCommentParentId(res.data.comment_view.comment) ?? 0, + true + ); } return s; }); diff --git a/src/shared/components/person/inbox.tsx b/src/shared/components/person/inbox.tsx index 7febf53a..f3fc2714 100644 --- a/src/shared/components/person/inbox.tsx +++ b/src/shared/components/person/inbox.tsx @@ -7,6 +7,7 @@ import { BanPerson, BanPersonResponse, BlockPerson, + CommentId, CommentReplyResponse, CommentReplyView, CommentReportResponse, @@ -64,6 +65,7 @@ import { editPrivateMessages, enableDownvotes, fetchLimit, + getCommentParentId, isBrowser, isInitialRoute, myAuth, @@ -114,6 +116,7 @@ interface InboxState { sort: CommentSortType; page: number; siteRes: GetSiteResponse; + finished: Map; } export class Inbox extends Component { @@ -128,6 +131,7 @@ export class Inbox extends Component { mentionsRes: { state: "empty" }, messagesRes: { state: "empty" }, markAllAsReadRes: { state: "empty" }, + finished: new Map(), }; constructor(props: any, context: any) { @@ -434,6 +438,7 @@ export class Inbox extends Component { { comment_view: i.view as CommentView, children: [], depth: 0 }, ]} viewType={CommentViewType.Flat} + finished={this.state.finished} noIndent markable showCommunity @@ -472,6 +477,7 @@ export class Inbox extends Component { depth: 0, }, ]} + finished={this.state.finished} viewType={CommentViewType.Flat} noIndent markable @@ -529,7 +535,9 @@ export class Inbox extends Component { ); } else { - return
{this.buildCombined().map(this.renderReplyType)}
; + return ( +
{this.buildCombined().map(r => this.renderReplyType(r))}
+ ); } } @@ -548,6 +556,7 @@ export class Inbox extends Component { { key={umv.person_mention.id} nodes={[{ comment_view: umv, children: [], depth: 0 }]} viewType={CommentViewType.Flat} + finished={this.state.finished} noIndent markable showCommunity @@ -683,7 +693,7 @@ export class Inbox extends Component { if (auth) { // It can be /u/me, or /username/1 let repliesForm: GetReplies = { - sort: "New", + sort, unread_only: true, page: 1, limit: fetchLimit, @@ -772,7 +782,7 @@ export class Inbox extends Component { ), }); - if (this.state.markAllAsReadRes.state == "success") { + if (i.state.markAllAsReadRes.state == "success") { i.setState({ repliesRes: { state: "empty" }, mentionsRes: { state: "empty" }, @@ -819,7 +829,8 @@ export class Inbox extends Component { const res = await apiWrapper(HttpService.client.createComment(form)); if (res.state == "success") { - toast(i18n.t("created")); + toast(i18n.t("reply_sent")); + this.findAndUpdateComment(res); } } @@ -828,6 +839,7 @@ export class Inbox extends Component { if (res.state == "success") { toast(i18n.t("edit")); + this.findAndUpdateComment(res); } } @@ -835,6 +847,7 @@ export class Inbox extends Component { const res = await apiWrapper(HttpService.client.deleteComment(form)); if (res.state == "success") { toast(i18n.t("deleted")); + this.findAndUpdateComment(res); } } @@ -842,6 +855,7 @@ export class Inbox extends Component { const res = await apiWrapper(HttpService.client.removeComment(form)); if (res.state == "success") { toast(i18n.t("remove_comment")); + this.findAndUpdateComment(res); } } @@ -1026,6 +1040,11 @@ export class Inbox extends Component { s.mentionsRes.data.mentions ); } + // Set finished for the parent + s.finished.set( + getCommentParentId(res.data.comment_view.comment) ?? 0, + true + ); return s; }); } diff --git a/src/shared/components/person/person-details.tsx b/src/shared/components/person/person-details.tsx index 9e990a61..4ff6fc5d 100644 --- a/src/shared/components/person/person-details.tsx +++ b/src/shared/components/person/person-details.tsx @@ -5,6 +5,7 @@ import { BanFromCommunity, BanPerson, BlockPerson, + CommentId, CommentView, CreateComment, CreateCommentLike, @@ -42,6 +43,7 @@ import { PostListing } from "../post/post-listing"; interface PersonDetailsProps { personRes: GetPersonDetailsResponse; + finished: Map; admins: PersonView[]; allLanguages: Language[]; siteLanguages: number[]; @@ -147,6 +149,7 @@ export class PersonDetails extends Component { key={i.id} nodes={[{ comment_view: c, children: [], depth: 0 }]} viewType={CommentViewType.Flat} + finished={this.props.finished} admins={this.props.admins} noBorder noIndent @@ -255,6 +258,7 @@ export class PersonDetails extends Component { nodes={commentsToFlatNodes(this.props.personRes.comments)} viewType={CommentViewType.Flat} admins={this.props.admins} + finished={this.props.finished} noIndent showCommunity showContext diff --git a/src/shared/components/person/profile.tsx b/src/shared/components/person/profile.tsx index 58e888e1..813d5227 100644 --- a/src/shared/components/person/profile.tsx +++ b/src/shared/components/person/profile.tsx @@ -11,6 +11,7 @@ import { BanPerson, BanPersonResponse, BlockPerson, + CommentId, CommentReplyResponse, CommentResponse, Community, @@ -65,6 +66,7 @@ import { enableNsfw, fetchLimit, futureDaysToUnixTime, + getCommentParentId, getPageFromString, getQueryParams, getQueryString, @@ -100,6 +102,7 @@ interface ProfileState { showBanDialog: boolean; removeData: boolean; siteRes: GetSiteResponse; + finished: Map; } interface ProfileProps { @@ -163,6 +166,7 @@ export class Profile extends Component< siteRes: this.isoData.site_res, showBanDialog: false, removeData: false, + finished: new Map(), }; constructor(props: RouteComponentProps<{ username: string }>, context: any) { @@ -333,6 +337,7 @@ export class Profile extends Component< sort={sort} page={page} limit={fetchLimit} + finished={this.state.finished} enableDownvotes={enableDownvotes(siteRes)} enableNsfw={enableNsfw(siteRes)} view={view} @@ -842,16 +847,7 @@ export class Profile extends Component< const createCommentRes = await apiWrapper( HttpService.client.createComment(form) ); - - this.setState(s => { - if ( - s.personRes.state == "success" && - createCommentRes.state == "success" - ) { - s.personRes.data.comments.unshift(createCommentRes.data.comment_view); - } - return s; - }); + this.createAndUpdateComments(createCommentRes); } async handleEditComment(form: EditComment) { @@ -1033,6 +1029,21 @@ export class Profile extends Component< res.data.comment_view, s.personRes.data.comments ); + s.finished.set(res.data.comment_view.comment.id, true); + } + return s; + }); + } + + createAndUpdateComments(res: RequestState) { + this.setState(s => { + if (s.personRes.state == "success" && res.state == "success") { + s.personRes.data.comments.unshift(res.data.comment_view); + // Set finished for the parent + s.finished.set( + getCommentParentId(res.data.comment_view.comment) ?? 0, + true + ); } return s; }); diff --git a/src/shared/components/person/reports.tsx b/src/shared/components/person/reports.tsx index dc7dfe09..dad5704c 100644 --- a/src/shared/components/person/reports.tsx +++ b/src/shared/components/person/reports.tsx @@ -17,7 +17,6 @@ import { ResolvePostReport, ResolvePrivateMessageReport, } from "lemmy-js-client"; -import { Subscription } from "rxjs"; import { i18n } from "../../i18next"; import { InitialFetchRequest } from "../../interfaces"; import { HttpService, UserService } from "../../services"; @@ -82,7 +81,6 @@ interface ReportsState { export class Reports extends Component { private isoData = setIsoData(this.context); - private subscription?: Subscription; state: ReportsState = { commentReportsRes: { state: "empty" }, postReportsRes: { state: "empty" }, @@ -130,9 +128,9 @@ export class Reports extends Component { } } - componentWillUnmount() { - if (isBrowser()) { - this.subscription?.unsubscribe(); + async componentDidMount() { + if (!isInitialRoute(this.isoData, this.context)) { + await this.refetch(); } } @@ -469,14 +467,14 @@ export class Reports extends Component { await this.refetch(); } - handleUnreadOrAllChange(i: Reports, event: any) { + async handleUnreadOrAllChange(i: Reports, event: any) { i.setState({ unreadOrAll: Number(event.target.value), page: 1 }); - i.refetch(); + await i.refetch(); } - handleMessageTypeChange(i: Reports, event: any) { + async handleMessageTypeChange(i: Reports, event: any) { i.setState({ messageType: Number(event.target.value), page: 1 }); - i.refetch(); + await i.refetch(); } static fetchInitialData(req: InitialFetchRequest): Promise[] { diff --git a/src/shared/components/post/post-report.tsx b/src/shared/components/post/post-report.tsx index 30970462..22fb73e4 100644 --- a/src/shared/components/post/post-report.tsx +++ b/src/shared/components/post/post-report.tsx @@ -1,4 +1,4 @@ -import { Component, linkEvent } from "inferno"; +import { Component, InfernoNode, linkEvent } from "inferno"; import { T } from "inferno-i18next-dess"; import { PostReportView, PostView, ResolvePostReport } from "lemmy-js-client"; import { i18n } from "../../i18next"; @@ -25,6 +25,14 @@ export class PostReport extends Component { super(props, context); } + componentWillReceiveProps( + nextProps: Readonly<{ children?: InfernoNode } & PostReportProps> + ): void { + if (this.props != nextProps) { + this.setState({ loading: false }); + } + } + render() { let r = this.props.report; let resolver = r.resolver; diff --git a/src/shared/components/post/post.tsx b/src/shared/components/post/post.tsx index 229b55ff..8c43534b 100644 --- a/src/shared/components/post/post.tsx +++ b/src/shared/components/post/post.tsx @@ -10,6 +10,7 @@ import { BanPersonResponse, BlockCommunity, BlockPerson, + CommentId, CommentReplyResponse, CommentResponse, CommentSortType, @@ -108,6 +109,7 @@ interface PostState { commentSectionRef?: RefObject; showSidebarMobile: boolean; maxCommentsShown: number; + finished: Map; } export class Post extends Component { @@ -124,6 +126,7 @@ export class Post extends Component { siteRes: this.isoData.site_res, showSidebarMobile: false, maxCommentsShown: commentsShownInterval, + finished: new Map(), }; constructor(props: any, context: any) { @@ -382,6 +385,7 @@ export class Post extends Component { siteLanguages={this.state.siteRes.discussion_languages} onCreateComment={this.handleCreateComment} onEditComment={this.handleEditComment} + finished={this.state.finished.get(0)} />