From 02fcfa26eee7e541c89f2bd645d6f2558b85bcf4 Mon Sep 17 00:00:00 2001 From: matc-pub <161147791+matc-pub@users.noreply.github.com> Date: Sun, 2 Jun 2024 17:46:32 +0200 Subject: [PATCH] Fix some submit button issues (#2487) * Prevent PostForm submit button spam * Keep CreatePost PostForm visible during submission * Keep PostListing PostForm visible during submission * Keep PostForm navigation warning enabled during submission * Remove `finished` from MarkdownTextAreaProps * Handle CommentForm submission failures * Keep CommentForm navigation warning enabled during submission * Handle PrivateMessageForm submission failures * Bypass navigation warning for successful CreatePrivateMessage * Fix absolute import, add eslint rule * Cleaner handleCommentSubmit --------- Co-authored-by: SleeplessOne1917 <28871516+SleeplessOne1917@users.noreply.github.com> --- eslint.config.mjs | 11 +++ .../components/comment/comment-form.tsx | 57 +++++++++------ .../components/comment/comment-node.tsx | 49 ++++++------- .../components/comment/comment-nodes.tsx | 3 - .../components/comment/comment-report.tsx | 1 - .../components/common/markdown-textarea.tsx | 38 +++------- src/shared/components/community/community.tsx | 18 ++--- src/shared/components/home/home.tsx | 18 ++--- src/shared/components/person/inbox.tsx | 27 ++++--- .../components/person/person-details.tsx | 4 -- src/shared/components/person/profile.tsx | 11 --- src/shared/components/post/create-post.tsx | 58 +++++++-------- src/shared/components/post/post-form.tsx | 71 ++++++++++++------- src/shared/components/post/post-listing.tsx | 4 +- src/shared/components/post/post.tsx | 38 ++++++---- .../create-private-message.tsx | 10 ++- .../private_message/private-message-form.tsx | 56 +++++++++------ .../private_message/private-message.tsx | 45 ++++++------ src/shared/components/search.tsx | 2 - 19 files changed, 270 insertions(+), 251 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index 15dfe2cd..c6a4e349 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -79,6 +79,17 @@ export default [ "unicorn/filename-case": 0, "jsx-a11y/media-has-caption": 0, "jsx-a11y/label-has-associated-control": 0, + "no-restricted-imports": [ + "error", + { + patterns: [ + { + group: ["assets/*", "client/*", "server/*", "shared/*"], + message: "Use relative import instead.", + }, + ], + }, + ], }, }, ]; diff --git a/src/shared/components/comment/comment-form.tsx b/src/shared/components/comment/comment-form.tsx index bc05a6f8..0cfbf8b0 100644 --- a/src/shared/components/comment/comment-form.tsx +++ b/src/shared/components/comment/comment-form.tsx @@ -2,18 +2,23 @@ import { capitalizeFirstLetter } from "@utils/helpers"; import { Component } from "inferno"; import { T } from "inferno-i18next-dess"; import { Link } from "inferno-router"; -import { CreateComment, EditComment, Language } from "lemmy-js-client"; +import { + CommentResponse, + CreateComment, + EditComment, + Language, +} from "lemmy-js-client"; import { CommentNodeI } from "../../interfaces"; import { I18NextService, UserService } from "../../services"; import { Icon } from "../common/icon"; import { MarkdownTextArea } from "../common/markdown-textarea"; +import { RequestState } from "../../services/HttpService"; 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; @@ -21,7 +26,9 @@ interface CommentFormProps { allLanguages: Language[]; siteLanguages: number[]; containerClass?: string; - onUpsertComment(form: EditComment | CreateComment): void; + onUpsertComment( + form: EditComment | CreateComment, + ): Promise>; } export class CommentForm extends Component { @@ -50,7 +57,6 @@ export class CommentForm extends Component { initialContent={initialContent} showLanguage buttonTitle={this.buttonTitle} - finished={this.props.finished} replyType={typeof this.props.node !== "number"} focus={this.props.focus} disabled={this.props.disabled} @@ -83,33 +89,38 @@ export class CommentForm extends Component { : capitalizeFirstLetter(I18NextService.i18n.t("reply")); } - handleCommentSubmit(content: string, language_id?: number) { + async handleCommentSubmit( + content: string, + language_id?: number, + ): Promise { const { node, onUpsertComment, edit } = this.props; + let response: RequestState; + if (typeof node === "number") { const post_id = node; - onUpsertComment({ + response = await onUpsertComment({ content, post_id, language_id, }); + } else if (edit) { + const comment_id = node.comment_view.comment.id; + response = await onUpsertComment({ + content, + comment_id, + language_id, + }); } else { - if (edit) { - const comment_id = node.comment_view.comment.id; - onUpsertComment({ - content, - comment_id, - language_id, - }); - } else { - const post_id = node.comment_view.post.id; - const parent_id = node.comment_view.comment.id; - this.props.onUpsertComment({ - content, - parent_id, - post_id, - language_id, - }); - } + const post_id = node.comment_view.post.id; + const parent_id = node.comment_view.comment.id; + response = await onUpsertComment({ + content, + parent_id, + post_id, + language_id, + }); } + + return response.state !== "failed"; } } diff --git a/src/shared/components/comment/comment-node.tsx b/src/shared/components/comment/comment-node.tsx index b14adc71..eb644363 100644 --- a/src/shared/components/comment/comment-node.tsx +++ b/src/shared/components/comment/comment-node.tsx @@ -2,7 +2,7 @@ import { colorList, getCommentParentId } from "@utils/app"; import { futureDaysToUnixTime, numToSI } from "@utils/helpers"; import classNames from "classnames"; import { isBefore, parseISO, subMinutes } from "date-fns"; -import { Component, InfernoNode, linkEvent } from "inferno"; +import { Component, linkEvent } from "inferno"; import { Link } from "inferno-router"; import { AddAdmin, @@ -33,7 +33,6 @@ import { SaveComment, TransferCommunity, } from "lemmy-js-client"; -import deepEqual from "lodash.isequal"; import { commentTreeMaxDepth } from "../../config"; import { CommentNodeI, @@ -87,7 +86,6 @@ interface CommentNodeProps { allLanguages: Language[]; siteLanguages: number[]; hideImages?: boolean; - finished: Map; onSaveComment(form: SaveComment): Promise; onCommentReplyRead(form: MarkCommentReplyAsRead): void; onPersonMentionRead(form: MarkPersonMentionAsRead): void; @@ -139,6 +137,8 @@ export class CommentNode extends Component { super(props, context); this.handleReplyCancel = this.handleReplyCancel.bind(this); + this.handleCreateComment = this.handleCreateComment.bind(this); + this.handleEditComment = this.handleEditComment.bind(this); this.handleReportComment = this.handleReportComment.bind(this); this.handleRemoveComment = this.handleRemoveComment.bind(this); this.handleReplyClick = this.handleReplyClick.bind(this); @@ -164,22 +164,6 @@ export class CommentNode extends Component { return this.commentView.comment.id; } - componentWillReceiveProps( - nextProps: Readonly<{ children?: InfernoNode } & CommentNodeProps>, - ): void { - if (!deepEqual(this.props, nextProps)) { - this.setState({ - showEdit: false, - showAdvanced: false, - createOrEditCommentLoading: false, - upvoteLoading: false, - downvoteLoading: false, - readLoading: false, - fetchChildrenLoading: false, - }); - } - } - render() { const node = this.props.node; const cv = this.commentView; @@ -283,12 +267,11 @@ export class CommentNode extends Component { edit onReplyCancel={this.handleReplyCancel} disabled={this.props.locked} - finished={this.props.finished.get(id)} focus allLanguages={this.props.allLanguages} siteLanguages={this.props.siteLanguages} containerClass="comment-comment-container" - onUpsertComment={this.props.onEditComment} + onUpsertComment={this.handleEditComment} /> )} {!this.state.showEdit && !this.state.collapsed && ( @@ -425,12 +408,11 @@ export class CommentNode extends Component { node={node} onReplyCancel={this.handleReplyCancel} disabled={this.props.locked} - finished={this.props.finished.get(id)} focus allLanguages={this.props.allLanguages} siteLanguages={this.props.siteLanguages} containerClass="comment-comment-container" - onUpsertComment={this.props.onCreateComment} + onUpsertComment={this.handleCreateComment} /> )} {!this.state.collapsed && node.children.length > 0 && ( @@ -447,7 +429,6 @@ export class CommentNode extends Component { hideImages={this.props.hideImages} isChild={!this.props.isTopLevel} depth={this.props.node.depth + 1} - finished={this.props.finished} onCommentReplyRead={this.props.onCommentReplyRead} onPersonMentionRead={this.props.onPersonMentionRead} onCreateComment={this.props.onCreateComment} @@ -559,6 +540,26 @@ export class CommentNode extends Component { this.setState({ showReply: false, showEdit: false }); } + async handleCreateComment( + form: CreateComment, + ): Promise> { + const res = await this.props.onCreateComment(form); + if (res.state !== "failed") { + this.setState({ showReply: false, showEdit: false }); + } + return res; + } + + async handleEditComment( + form: EditComment, + ): Promise> { + const res = await this.props.onEditComment(form); + if (res.state !== "failed") { + this.setState({ showReply: false, showEdit: false }); + } + return res; + } + isPersonMentionType(item: CommentNodeView): item is PersonMentionView { return item.person_mention?.id !== undefined; } diff --git a/src/shared/components/comment/comment-nodes.tsx b/src/shared/components/comment/comment-nodes.tsx index d985ff99..5f31f587 100644 --- a/src/shared/components/comment/comment-nodes.tsx +++ b/src/shared/components/comment/comment-nodes.tsx @@ -7,7 +7,6 @@ import { BanFromCommunity, BanPerson, BlockPerson, - CommentId, CommentResponse, CommunityModeratorView, CreateComment, @@ -52,7 +51,6 @@ interface CommentNodesProps { hideImages?: boolean; isChild?: boolean; depth?: number; - finished: Map; onSaveComment(form: SaveComment): Promise; onCommentReplyRead(form: MarkCommentReplyAsRead): void; onPersonMentionRead(form: MarkPersonMentionAsRead): void; @@ -124,7 +122,6 @@ 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 8af10fa3..34b4cd5c 100644 --- a/src/shared/components/comment/comment-report.tsx +++ b/src/shared/components/comment/comment-report.tsx @@ -90,7 +90,6 @@ export class CommentReport extends Component< siteLanguages={[]} hideImages // All of these are unused, since its viewonly - finished={new Map()} onSaveComment={async () => {}} onBlockPerson={async () => {}} onDeleteComment={async () => {}} diff --git a/src/shared/components/common/markdown-textarea.tsx b/src/shared/components/common/markdown-textarea.tsx index abc945c9..d6987e19 100644 --- a/src/shared/components/common/markdown-textarea.tsx +++ b/src/shared/components/common/markdown-textarea.tsx @@ -3,7 +3,7 @@ import { numToSI, randomStr } from "@utils/helpers"; import autosize from "autosize"; import classNames from "classnames"; import { NoOptionI18nKeys } from "i18next"; -import { Component, InfernoNode, linkEvent } from "inferno"; +import { Component, linkEvent } from "inferno"; import { Prompt } from "inferno-router"; import { Language } from "lemmy-js-client"; import { @@ -41,7 +41,6 @@ interface MarkdownTextAreaProps { replyType?: boolean; focus?: boolean; disabled?: boolean; - finished?: boolean; /** * Whether to show the language selector */ @@ -49,7 +48,7 @@ interface MarkdownTextAreaProps { hideNavigationWarnings?: boolean; onContentChange?(val: string): void; onReplyCancel?(): void; - onSubmit?(content: string, languageId?: number): void; + onSubmit?(content: string, languageId?: number): Promise; allLanguages: Language[]; // TODO should probably be nullable siteLanguages: number[]; // TODO same } @@ -115,27 +114,6 @@ export class MarkdownTextArea extends Component< } } - componentWillReceiveProps( - nextProps: MarkdownTextAreaProps & { children?: InfernoNode }, - ) { - if (nextProps.finished) { - this.setState({ - previewMode: false, - imageUploadStatus: undefined, - loading: false, - content: undefined, - }); - if (this.props.replyType) { - this.props.onReplyCancel?.(); - } - - const textarea: any = document.getElementById(this.id); - const form: any = document.getElementById(this.formId); - form.reset(); - setTimeout(() => autosize.update(textarea), 10); - } - } - render() { const languageId = this.state.languageId; @@ -149,8 +127,8 @@ export class MarkdownTextArea extends Component< message={I18NextService.i18n.t("block_leaving")} when={ !this.props.hideNavigationWarnings && - !!this.state.content && - !this.state.submitted + ((!!this.state.content && !this.state.submitted) || + this.state.loading) } />
@@ -575,11 +553,15 @@ export class MarkdownTextArea extends Component< this.setState({ languageId: val[0] }); } - handleSubmit(i: MarkdownTextArea, event: any) { + async handleSubmit(i: MarkdownTextArea, event: any) { event.preventDefault(); if (i.state.content) { i.setState({ loading: true, submitted: true }); - i.props.onSubmit?.(i.state.content, i.state.languageId); + const success = await i.props.onSubmit?.( + i.state.content, + i.state.languageId, + ); + i.setState({ loading: false, submitted: success ?? true }); } } diff --git a/src/shared/components/community/community.tsx b/src/shared/components/community/community.tsx index 59561765..67a16618 100644 --- a/src/shared/components/community/community.tsx +++ b/src/shared/components/community/community.tsx @@ -6,7 +6,6 @@ import { editWith, enableDownvotes, enableNsfw, - getCommentParentId, getDataTypeString, postToCommentSortType, setIsoData, @@ -42,7 +41,6 @@ import { BanPersonResponse, BlockCommunity, BlockPerson, - CommentId, CommentReplyResponse, CommentResponse, CommunityResponse, @@ -136,7 +134,6 @@ interface State { commentsRes: RequestState; siteRes: GetSiteResponse; showSidebarMobile: boolean; - finished: Map; isIsomorphic: boolean; } @@ -201,7 +198,6 @@ export class Community extends Component { commentsRes: EMPTY_REQUEST, siteRes: this.isoData.site_res, showSidebarMobile: false, - finished: new Map(), isIsomorphic: false, }; private readonly mainContentRef: RefObject; @@ -528,7 +524,6 @@ export class Community extends Component { { const createCommentRes = await HttpService.client.createComment(form); this.createAndUpdateComments(createCommentRes); + if (createCommentRes.state === "failed") { + toast(I18NextService.i18n.t(createCommentRes.err.message), "danger"); + } return createCommentRes; } @@ -827,6 +825,9 @@ export class Community extends Component { const editCommentRes = await HttpService.client.editComment(form); this.findAndUpdateCommentEdit(editCommentRes); + if (editCommentRes.state === "failed") { + toast(I18NextService.i18n.t(editCommentRes.err.message), "danger"); + } return editCommentRes; } @@ -1038,7 +1039,6 @@ 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; }); @@ -1060,12 +1060,6 @@ export class Community extends Component { 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 1c773ba6..22471be9 100644 --- a/src/shared/components/home/home.tsx +++ b/src/shared/components/home/home.tsx @@ -5,7 +5,6 @@ import { editWith, enableDownvotes, enableNsfw, - getCommentParentId, getDataTypeString, myAuth, postToCommentSortType, @@ -37,7 +36,6 @@ import { BanPerson, BanPersonResponse, BlockPerson, - CommentId, CommentReplyResponse, CommentResponse, CreateComment, @@ -125,7 +123,6 @@ interface HomeState { subscribedCollapsed: boolean; tagline?: string; siteRes: GetSiteResponse; - finished: Map; isIsomorphic: boolean; } @@ -274,7 +271,6 @@ export class Home extends Component { showTrendingMobile: false, showSidebarMobile: false, subscribedCollapsed: false, - finished: new Map(), isIsomorphic: false, }; @@ -770,7 +766,6 @@ export class Home extends Component { { const createCommentRes = await HttpService.client.createComment(form); this.createAndUpdateComments(createCommentRes); + if (createCommentRes.state === "failed") { + toast(I18NextService.i18n.t(createCommentRes.err.message), "danger"); + } return createCommentRes; } @@ -980,6 +978,9 @@ export class Home extends Component { const editCommentRes = await HttpService.client.editComment(form); this.findAndUpdateCommentEdit(editCommentRes); + if (editCommentRes.state === "failed") { + toast(I18NextService.i18n.t(editCommentRes.err.message), "danger"); + } return editCommentRes; } @@ -1168,7 +1169,6 @@ 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; }); @@ -1190,12 +1190,6 @@ export class Home extends Component { 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 aaa1a614..e0b87afe 100644 --- a/src/shared/components/person/inbox.tsx +++ b/src/shared/components/person/inbox.tsx @@ -5,7 +5,6 @@ import { editPrivateMessage, editWith, enableDownvotes, - getCommentParentId, myAuth, setIsoData, updatePersonBlock, @@ -28,7 +27,6 @@ import { BanPerson, BanPersonResponse, BlockPerson, - CommentId, CommentReplyResponse, CommentReplyView, CommentReportResponse, @@ -132,7 +130,6 @@ interface InboxState { sort: CommentSortType; page: number; siteRes: GetSiteResponse; - finished: Map; isIsomorphic: boolean; } @@ -157,7 +154,6 @@ export class Inbox extends Component { mentionsRes: EMPTY_REQUEST, messagesRes: EMPTY_REQUEST, markAllAsReadRes: EMPTY_REQUEST, - finished: new Map(), isIsomorphic: false, }; @@ -512,7 +508,6 @@ export class Inbox extends Component { { comment_view: i.view as CommentView, children: [], depth: 0 }, ]} viewType={CommentViewType.Flat} - finished={this.state.finished} markable showCommunity showContext @@ -551,7 +546,6 @@ export class Inbox extends Component { depth: 0, }, ]} - finished={this.state.finished} viewType={CommentViewType.Flat} markable showCommunity @@ -623,7 +617,6 @@ export class Inbox extends Component { { key={umv.person_mention.id} nodes={[{ comment_view: umv, children: [], depth: 0 }]} viewType={CommentViewType.Flat} - finished={this.state.finished} markable showCommunity showContext @@ -996,9 +988,13 @@ export class Inbox extends Component { this.findAndUpdateMessage(res); } - async handleEditMessage(form: EditPrivateMessage) { + async handleEditMessage(form: EditPrivateMessage): Promise { const res = await HttpService.client.editPrivateMessage(form); this.findAndUpdateMessage(res); + if (res.state === "failed") { + toast(I18NextService.i18n.t(res.err.message), "danger"); + } + return res.state !== "failed"; } async handleMarkMessageAsRead(form: MarkPrivateMessageAsRead) { @@ -1015,7 +1011,7 @@ export class Inbox extends Component { this.reportToast(res); } - async handleCreateMessage(form: CreatePrivateMessage) { + async handleCreateMessage(form: CreatePrivateMessage): Promise { const res = await HttpService.client.createPrivateMessage(form); this.setState(s => { if (s.messagesRes.state === "success" && res.state === "success") { @@ -1026,6 +1022,10 @@ export class Inbox extends Component { return s; }); + if (res.state === "failed") { + toast(I18NextService.i18n.t(res.err.message), "danger"); + } + return res.state !== "failed"; } findAndUpdateMessage(res: RequestState) { @@ -1094,6 +1094,8 @@ export class Inbox extends Component { ) { if (res.state === "success") { toast(I18NextService.i18n.t("report_created")); + } else if (res.state === "failed") { + toast(I18NextService.i18n.t(res.err.message), "danger"); } } @@ -1113,11 +1115,6 @@ 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 4227a157..67aab710 100644 --- a/src/shared/components/person/person-details.tsx +++ b/src/shared/components/person/person-details.tsx @@ -6,7 +6,6 @@ import { BanFromCommunity, BanPerson, BlockPerson, - CommentId, CommentResponse, CommentView, CreateComment, @@ -49,7 +48,6 @@ import { RequestState } from "../../services/HttpService"; interface PersonDetailsProps { personRes: GetPersonDetailsResponse; - finished: Map; admins: PersonView[]; allLanguages: Language[]; siteLanguages: number[]; @@ -153,7 +151,6 @@ 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 showCommunity @@ -266,7 +263,6 @@ export class PersonDetails extends Component { nodes={commentsToFlatNodes(this.props.personRes.comments)} viewType={CommentViewType.Flat} admins={this.props.admins} - finished={this.props.finished} isTopLevel showCommunity showContext diff --git a/src/shared/components/person/profile.tsx b/src/shared/components/person/profile.tsx index 861c9367..188bb820 100644 --- a/src/shared/components/person/profile.tsx +++ b/src/shared/components/person/profile.tsx @@ -4,7 +4,6 @@ import { editWith, enableDownvotes, enableNsfw, - getCommentParentId, setIsoData, updatePersonBlock, voteDisplayMode, @@ -38,7 +37,6 @@ import { BanPerson, BanPersonResponse, BlockPerson, - CommentId, CommentReplyResponse, CommentResponse, Community, @@ -120,7 +118,6 @@ interface ProfileState { showBanDialog: boolean; removeData: boolean; siteRes: GetSiteResponse; - finished: Map; isIsomorphic: boolean; } @@ -206,7 +203,6 @@ export class Profile extends Component { siteRes: this.isoData.site_res, showBanDialog: false, removeData: false, - finished: new Map(), isIsomorphic: false, }; @@ -491,7 +487,6 @@ export class Profile extends Component { sort={sort} page={page} limit={fetchLimit} - finished={this.state.finished} enableDownvotes={enableDownvotes(siteRes)} voteDisplayMode={voteDisplayMode(siteRes)} enableNsfw={enableNsfw(siteRes)} @@ -1178,7 +1173,6 @@ 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; }); @@ -1200,11 +1194,6 @@ export class Profile extends Component { 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/post/create-post.tsx b/src/shared/components/post/create-post.tsx index a4e533df..b51d5852 100644 --- a/src/shared/components/post/create-post.tsx +++ b/src/shared/components/post/create-post.tsx @@ -27,7 +27,6 @@ import { wrapClient, } from "../../services/HttpService"; import { HtmlTags } from "../common/html-tags"; -import { Spinner } from "../common/icon"; import { PostForm } from "./post-form"; import { getHttpBaseInternal } from "../../utils/env"; import { IRoutePropsWithFetch } from "../../routes"; @@ -178,39 +177,28 @@ export class CreatePost extends Component< title={this.documentTitle} path={this.context.router.route.match.url} /> - {this.state.loading ? ( -
- -
- ) : ( -
-
-

- {I18NextService.i18n.t("create_post")} -

- -
+
+
+

{I18NextService.i18n.t("create_post")}

+
- )} +
); } @@ -242,11 +230,13 @@ export class CreatePost extends Component< }); } - async handlePostCreate(form: CreatePostI) { + async handlePostCreate(form: CreatePostI, bypassNavWarning: () => void) { + this.setState({ loading: true }); const res = await HttpService.client.createPost(form); if (res.state === "success") { const postId = res.data.post_view.post.id; + bypassNavWarning(); this.props.history.replace(`/post/${postId}`); } else if (res.state === "failed") { this.setState({ diff --git a/src/shared/components/post/post-form.tsx b/src/shared/components/post/post-form.tsx index 102911e9..f5fe8b71 100644 --- a/src/shared/components/post/post-form.tsx +++ b/src/shared/components/post/post-form.tsx @@ -54,8 +54,8 @@ interface PostFormProps { siteLanguages: number[]; params?: PostFormParams; onCancel?(): void; - onCreate?(form: CreatePost): void; - onEdit?(form: EditPost): void; + onCreate?(form: CreatePost, bypassNavWarning: () => void): void; + onEdit?(form: EditPost, bypassNavWarning: () => void): void; enableNsfw?: boolean; enableDownvotes?: boolean; voteDisplayMode: LocalUserVoteDisplayMode; @@ -85,6 +85,7 @@ interface PostFormState { communitySearchOptions: Choice[]; previewMode: boolean; submitted: boolean; + bypassNavWarning: boolean; } function handlePostSubmit(i: PostForm, event: any) { @@ -93,34 +94,46 @@ function handlePostSubmit(i: PostForm, event: any) { if ((i.state.form.url ?? "") === "") { i.setState(s => ((s.form.url = undefined), s)); } + // This forces `props.loading` to become true, then false, to enable the + // submit button again. i.setState({ submitted: true }); const pForm = i.state.form; const pv = i.props.post_view; if (pv) { - i.props.onEdit?.({ - post_id: pv.post.id, - name: pForm.name, - url: pForm.url, - body: pForm.body, - nsfw: pForm.nsfw, - language_id: pForm.language_id, - custom_thumbnail: pForm.custom_thumbnail, - alt_text: pForm.alt_text, - }); + i.props.onEdit?.( + { + post_id: pv.post.id, + name: pForm.name, + url: pForm.url, + body: pForm.body, + nsfw: pForm.nsfw, + language_id: pForm.language_id, + custom_thumbnail: pForm.custom_thumbnail, + alt_text: pForm.alt_text, + }, + () => { + i.setState({ bypassNavWarning: true }); + }, + ); } else if (pForm.name && pForm.community_id) { - i.props.onCreate?.({ - name: pForm.name, - community_id: pForm.community_id, - url: pForm.url, - body: pForm.body, - nsfw: pForm.nsfw, - language_id: pForm.language_id, - honeypot: pForm.honeypot, - custom_thumbnail: pForm.custom_thumbnail, - alt_text: pForm.alt_text, - }); + i.props.onCreate?.( + { + name: pForm.name, + community_id: pForm.community_id, + url: pForm.url, + body: pForm.body, + nsfw: pForm.nsfw, + language_id: pForm.language_id, + honeypot: pForm.honeypot, + custom_thumbnail: pForm.custom_thumbnail, + alt_text: pForm.alt_text, + }, + () => { + i.setState({ bypassNavWarning: true }); + }, + ); } } @@ -247,6 +260,7 @@ export class PostForm extends Component { previewMode: false, communitySearchOptions: [], submitted: false, + bypassNavWarning: false, }; postTitleRef = createRef(); @@ -347,6 +361,9 @@ export class PostForm extends Component { nextProps.initialCommunities?.map(communityToChoice) ?? [], }); } + if (this.props.loading && !nextProps.loading) { + this.setState({ submitted: false, bypassNavWarning: false }); + } } render() { @@ -364,7 +381,7 @@ export class PostForm extends Component { this.state.form.name || this.state.form.url || this.state.form.body - ) && !this.state.submitted + ) && !this.state.bypassNavWarning } />
@@ -618,7 +635,11 @@ export class PostForm extends Component {