]> Untitled Git - lemmy-ui.git/blob - src/shared/components/post/create-post.tsx
Merge branch 'main' into route-data-refactor
[lemmy-ui.git] / src / shared / components / post / create-post.tsx
1 import { Component } from "inferno";
2 import { RouteComponentProps } from "inferno-router/dist/Route";
3 import {
4   CreatePost as CreatePostI,
5   GetCommunity,
6   GetCommunityResponse,
7   GetSiteResponse,
8   ListCommunitiesResponse,
9 } from "lemmy-js-client";
10 import { i18n } from "../../i18next";
11 import { InitialFetchRequest, PostFormParams } from "../../interfaces";
12 import { FirstLoadService } from "../../services/FirstLoadService";
13 import {
14   HttpService,
15   RequestState,
16   WrappedLemmyHttp,
17 } from "../../services/HttpService";
18 import {
19   Choice,
20   QueryParams,
21   RouteDataResponse,
22   enableDownvotes,
23   enableNsfw,
24   getIdFromString,
25   getQueryParams,
26   myAuth,
27   setIsoData,
28 } from "../../utils";
29 import { HtmlTags } from "../common/html-tags";
30 import { Spinner } from "../common/icon";
31 import { PostForm } from "./post-form";
32
33 export interface CreatePostProps {
34   communityId?: number;
35 }
36
37 type CreatePostData = RouteDataResponse<{
38   communityResponse: GetCommunityResponse;
39   initialCommunitiesRes: ListCommunitiesResponse;
40 }>;
41
42 function getCreatePostQueryParams() {
43   return getQueryParams<CreatePostProps>({
44     communityId: getIdFromString,
45   });
46 }
47
48 function fetchCommunitiesForOptions(client: WrappedLemmyHttp) {
49   return client.listCommunities({ limit: 30, sort: "TopMonth", type_: "All" });
50 }
51
52 interface CreatePostState {
53   siteRes: GetSiteResponse;
54   loading: boolean;
55   selectedCommunityChoice?: Choice;
56   initialCommunitiesRes: RequestState<ListCommunitiesResponse>;
57   isIsomorphic: boolean;
58 }
59
60 export class CreatePost extends Component<
61   RouteComponentProps<Record<string, never>>,
62   CreatePostState
63 > {
64   private isoData = setIsoData<CreatePostData>(this.context);
65   state: CreatePostState = {
66     siteRes: this.isoData.site_res,
67     loading: true,
68     initialCommunitiesRes: { state: "empty" },
69     isIsomorphic: false,
70   };
71
72   constructor(props: RouteComponentProps<Record<string, never>>, context: any) {
73     super(props, context);
74
75     this.handlePostCreate = this.handlePostCreate.bind(this);
76     this.handleSelectedCommunityChange =
77       this.handleSelectedCommunityChange.bind(this);
78
79     // Only fetch the data if coming from another route
80     if (FirstLoadService.isFirstLoad) {
81       const { communityResponse: communityRes, initialCommunitiesRes } =
82         this.isoData.routeData;
83
84       this.state = {
85         ...this.state,
86         loading: false,
87         initialCommunitiesRes,
88         isIsomorphic: true,
89       };
90
91       if (communityRes?.state === "success") {
92         const communityChoice: Choice = {
93           label: communityRes.data.community_view.community.title,
94           value: communityRes.data.community_view.community.id.toString(),
95         };
96
97         this.state = {
98           ...this.state,
99           selectedCommunityChoice: communityChoice,
100         };
101       }
102     }
103   }
104
105   async fetchCommunity() {
106     const { communityId } = getCreatePostQueryParams();
107     const auth = myAuth();
108
109     if (communityId) {
110       const res = await HttpService.client.getCommunity({
111         id: communityId,
112         auth,
113       });
114       if (res.state === "success") {
115         this.setState({
116           selectedCommunityChoice: {
117             label: res.data.community_view.community.name,
118             value: res.data.community_view.community.id.toString(),
119           },
120           loading: false,
121         });
122       }
123     }
124   }
125
126   async componentDidMount() {
127     // TODO test this
128     if (!this.state.isIsomorphic) {
129       const { communityId } = getCreatePostQueryParams();
130
131       const initialCommunitiesRes = await fetchCommunitiesForOptions(
132         HttpService.client
133       );
134
135       this.setState({
136         initialCommunitiesRes,
137       });
138
139       if (
140         communityId?.toString() !== this.state.selectedCommunityChoice?.value
141       ) {
142         await this.fetchCommunity();
143       } else if (!communityId) {
144         this.setState({
145           selectedCommunityChoice: undefined,
146           loading: false,
147         });
148       }
149     }
150   }
151
152   get documentTitle(): string {
153     return `${i18n.t("create_post")} - ${
154       this.state.siteRes.site_view.site.name
155     }`;
156   }
157
158   render() {
159     const { selectedCommunityChoice } = this.state;
160
161     const locationState = this.props.history.location.state as
162       | PostFormParams
163       | undefined;
164
165     return (
166       <div className="container-lg">
167         <HtmlTags
168           title={this.documentTitle}
169           path={this.context.router.route.match.url}
170         />
171         {this.state.loading ? (
172           <h5>
173             <Spinner large />
174           </h5>
175         ) : (
176           <div className="row">
177             <div className="col-12 col-lg-6 offset-lg-3 mb-4">
178               <h5>{i18n.t("create_post")}</h5>
179               <PostForm
180                 onCreate={this.handlePostCreate}
181                 params={locationState}
182                 enableDownvotes={enableDownvotes(this.state.siteRes)}
183                 enableNsfw={enableNsfw(this.state.siteRes)}
184                 allLanguages={this.state.siteRes.all_languages}
185                 siteLanguages={this.state.siteRes.discussion_languages}
186                 selectedCommunityChoice={selectedCommunityChoice}
187                 onSelectCommunity={this.handleSelectedCommunityChange}
188                 initialCommunities={
189                   this.state.initialCommunitiesRes.state === "success"
190                     ? this.state.initialCommunitiesRes.data.communities
191                     : []
192                 }
193               />
194             </div>
195           </div>
196         )}
197       </div>
198     );
199   }
200
201   async updateUrl({ communityId }: Partial<CreatePostProps>) {
202     const { communityId: urlCommunityId } = getCreatePostQueryParams();
203
204     const locationState = this.props.history.location.state as
205       | PostFormParams
206       | undefined;
207
208     const url = new URL(location.href);
209
210     const newId = (communityId ?? urlCommunityId)?.toString();
211
212     if (newId !== undefined) {
213       url.searchParams.set("communityId", newId);
214     } else {
215       url.searchParams.delete("communityId");
216     }
217
218     history.replaceState(locationState, "", url);
219
220     await this.fetchCommunity();
221   }
222
223   handleSelectedCommunityChange(choice: Choice) {
224     this.updateUrl({
225       communityId: getIdFromString(choice?.value),
226     });
227   }
228
229   async handlePostCreate(form: CreatePostI) {
230     const res = await HttpService.client.createPost(form);
231
232     if (res.state === "success") {
233       const postId = res.data.post_view.post.id;
234       this.props.history.replace(`/post/${postId}`);
235     } else {
236       this.setState({
237         loading: false,
238       });
239     }
240   }
241
242   static async fetchInitialData({
243     client,
244     query: { communityId },
245     auth,
246   }: InitialFetchRequest<
247     QueryParams<CreatePostProps>
248   >): Promise<CreatePostData> {
249     const data: CreatePostData = {
250       initialCommunitiesRes: await fetchCommunitiesForOptions(client),
251       communityResponse: { state: "empty" },
252     };
253
254     if (communityId) {
255       const form: GetCommunity = {
256         auth,
257         id: getIdFromString(communityId),
258       };
259
260       data.communityResponse = await client.getCommunity(form);
261     }
262
263     return data;
264   }
265 }