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