1 import { None, Option, Some } from "@sniptt/monads";
2 import { Component, linkEvent } from "inferno";
3 import { Prompt } from "inferno-router";
14 } from "lemmy-js-client";
15 import { Subscription } from "rxjs";
16 import { i18n } from "../../i18next";
17 import { UserService, WebSocketService } from "../../services";
20 capitalizeFirstLetter,
25 import { Icon, Spinner } from "../common/icon";
26 import { ImageUploadForm } from "../common/image-upload-form";
27 import { LanguageSelect } from "../common/language-select";
28 import { MarkdownTextArea } from "../common/markdown-textarea";
30 interface CommunityFormProps {
31 community_view: Option<CommunityView>; // If a community is given, that means this is an edit
32 allLanguages: Language[];
33 siteLanguages: number[];
34 communityLanguages: Option<number[]>;
36 onCreate?(community: CommunityView): any;
37 onEdit?(community: CommunityView): any;
41 interface CommunityFormState {
42 communityForm: CreateCommunity;
46 export class CommunityForm extends Component<
50 private id = `community-form-${randomStr()}`;
51 private subscription: Subscription;
53 private emptyState: CommunityFormState = {
54 communityForm: new CreateCommunity({
58 discussion_languages: this.props.communityLanguages,
62 posting_restricted_to_mods: None,
68 constructor(props: any, context: any) {
69 super(props, context);
71 this.state = this.emptyState;
73 this.handleCommunityDescriptionChange =
74 this.handleCommunityDescriptionChange.bind(this);
76 this.handleIconUpload = this.handleIconUpload.bind(this);
77 this.handleIconRemove = this.handleIconRemove.bind(this);
79 this.handleBannerUpload = this.handleBannerUpload.bind(this);
80 this.handleBannerRemove = this.handleBannerRemove.bind(this);
82 this.handleDiscussionLanguageChange =
83 this.handleDiscussionLanguageChange.bind(this);
85 this.parseMessage = this.parseMessage.bind(this);
86 this.subscription = wsSubscribe(this.parseMessage);
88 if (this.props.community_view.isSome()) {
89 let cv = this.props.community_view.unwrap();
92 communityForm: new CreateCommunity({
93 name: cv.community.name,
94 title: cv.community.title,
95 description: cv.community.description,
96 nsfw: Some(cv.community.nsfw),
97 icon: cv.community.icon,
98 banner: cv.community.banner,
99 posting_restricted_to_mods: Some(
100 cv.community.posting_restricted_to_mods
102 discussion_languages: this.props.communityLanguages,
109 componentDidUpdate() {
111 !this.state.loading &&
112 (this.state.communityForm.name ||
113 this.state.communityForm.title ||
114 this.state.communityForm.description.isSome())
116 window.onbeforeunload = () => true;
118 window.onbeforeunload = undefined;
122 componentWillUnmount() {
123 this.subscription.unsubscribe();
124 window.onbeforeunload = null;
132 !this.state.loading &&
133 (this.state.communityForm.name ||
134 this.state.communityForm.title ||
135 this.state.communityForm.description.isSome())
137 message={i18n.t("block_leaving")}
139 <form onSubmit={linkEvent(this, this.handleCreateCommunitySubmit)}>
140 {this.props.community_view.isNone() && (
141 <div className="form-group row">
143 className="col-12 col-sm-2 col-form-label"
144 htmlFor="community-name"
148 className="position-absolute pointer unselectable ml-2 text-muted"
149 data-tippy-content={i18n.t("name_explain")}
151 <Icon icon="help-circle" classes="icon-inline" />
154 <div className="col-12 col-sm-10">
158 className="form-control"
159 value={this.state.communityForm.name}
160 onInput={linkEvent(this, this.handleCommunityNameChange)}
164 title={i18n.t("community_reqs")}
169 <div className="form-group row">
171 className="col-12 col-sm-2 col-form-label"
172 htmlFor="community-title"
174 {i18n.t("display_name")}
176 className="position-absolute pointer unselectable ml-2 text-muted"
177 data-tippy-content={i18n.t("display_name_explain")}
179 <Icon icon="help-circle" classes="icon-inline" />
182 <div className="col-12 col-sm-10">
186 value={this.state.communityForm.title}
187 onInput={linkEvent(this, this.handleCommunityTitleChange)}
188 className="form-control"
195 <div className="form-group row">
196 <label className="col-12 col-sm-2">{i18n.t("icon")}</label>
197 <div className="col-12 col-sm-10">
199 uploadTitle={i18n.t("upload_icon")}
200 imageSrc={this.state.communityForm.icon}
201 onUpload={this.handleIconUpload}
202 onRemove={this.handleIconRemove}
207 <div className="form-group row">
208 <label className="col-12 col-sm-2">{i18n.t("banner")}</label>
209 <div className="col-12 col-sm-10">
211 uploadTitle={i18n.t("upload_banner")}
212 imageSrc={this.state.communityForm.banner}
213 onUpload={this.handleBannerUpload}
214 onRemove={this.handleBannerRemove}
218 <div className="form-group row">
219 <label className="col-12 col-sm-2 col-form-label" htmlFor={this.id}>
222 <div className="col-12 col-sm-10">
224 initialContent={this.state.communityForm.description}
225 initialLanguageId={None}
226 placeholder={Some("description")}
229 onContentChange={this.handleCommunityDescriptionChange}
236 {this.props.enableNsfw && (
237 <div className="form-group row">
238 <legend className="col-form-label col-sm-2 pt-0">
241 <div className="col-10">
242 <div className="form-check">
244 className="form-check-input position-static"
247 checked={toUndefined(this.state.communityForm.nsfw)}
248 onChange={linkEvent(this, this.handleCommunityNsfwChange)}
254 <div className="form-group row">
255 <legend className="col-form-label col-6 pt-0">
256 {i18n.t("only_mods_can_post_in_community")}
258 <div className="col-6">
259 <div className="form-check">
261 className="form-check-input position-static"
262 id="community-only-mods-can-post"
264 checked={toUndefined(
265 this.state.communityForm.posting_restricted_to_mods
269 this.handleCommunityPostingRestrictedToMods
276 allLanguages={this.props.allLanguages}
277 siteLanguages={this.props.siteLanguages}
279 selectedLanguageIds={this.state.communityForm.discussion_languages}
281 onChange={this.handleDiscussionLanguageChange}
283 <div className="form-group row">
284 <div className="col-12">
287 className="btn btn-secondary mr-2"
288 disabled={this.state.loading}
290 {this.state.loading ? (
292 ) : this.props.community_view.isSome() ? (
293 capitalizeFirstLetter(i18n.t("save"))
295 capitalizeFirstLetter(i18n.t("create"))
298 {this.props.community_view.isSome() && (
301 className="btn btn-secondary"
302 onClick={linkEvent(this, this.handleCancel)}
314 handleCreateCommunitySubmit(i: CommunityForm, event: any) {
315 event.preventDefault();
316 i.setState({ loading: true });
317 let cForm = i.state.communityForm;
318 cForm.auth = auth().unwrap();
320 i.props.community_view.match({
322 let form = new EditCommunity({
323 community_id: cv.community.id,
324 title: Some(cForm.title),
325 description: cForm.description,
327 banner: cForm.banner,
329 posting_restricted_to_mods: cForm.posting_restricted_to_mods,
330 discussion_languages: cForm.discussion_languages,
334 WebSocketService.Instance.send(wsClient.editCommunity(form));
337 WebSocketService.Instance.send(
338 wsClient.createCommunity(i.state.communityForm)
345 handleCommunityNameChange(i: CommunityForm, event: any) {
346 i.state.communityForm.name = event.target.value;
350 handleCommunityTitleChange(i: CommunityForm, event: any) {
351 i.state.communityForm.title = event.target.value;
355 handleCommunityDescriptionChange(val: string) {
356 this.setState(s => ((s.communityForm.description = Some(val)), s));
359 handleCommunityNsfwChange(i: CommunityForm, event: any) {
360 i.state.communityForm.nsfw = Some(event.target.checked);
364 handleCommunityPostingRestrictedToMods(i: CommunityForm, event: any) {
365 i.state.communityForm.posting_restricted_to_mods = Some(
371 handleCancel(i: CommunityForm) {
375 handleIconUpload(url: string) {
376 this.setState(s => ((s.communityForm.icon = Some(url)), s));
380 this.setState(s => ((s.communityForm.icon = Some("")), s));
383 handleBannerUpload(url: string) {
384 this.setState(s => ((s.communityForm.banner = Some(url)), s));
387 handleBannerRemove() {
388 this.setState(s => ((s.communityForm.banner = Some("")), s));
391 handleDiscussionLanguageChange(val: number[]) {
392 this.setState(s => ((s.communityForm.discussion_languages = Some(val)), s));
395 parseMessage(msg: any) {
396 let op = wsUserOp(msg);
399 // Errors handled by top level pages
400 // toast(i18n.t(msg.error), "danger");
401 this.setState({ loading: false });
403 } else if (op == UserOperation.CreateCommunity) {
404 let data = wsJsonToRes<CommunityResponse>(msg, CommunityResponse);
405 this.props.onCreate(data.community_view);
408 let community = data.community_view.community;
410 UserService.Instance.myUserInfo.match({
412 let person = mui.local_user_view.person;
424 } else if (op == UserOperation.EditCommunity) {
425 let data = wsJsonToRes<CommunityResponse>(msg, CommunityResponse);
426 this.setState({ loading: false });
427 this.props.onEdit(data.community_view);
428 let community = data.community_view.community;
430 UserService.Instance.myUserInfo.match({
432 let followFound = mui.follows.findIndex(
433 f => f.community.id == community.id
436 mui.follows[followFound].community = community;
439 let moderatesFound = mui.moderates.findIndex(
440 f => f.community.id == community.id
442 if (moderatesFound) {
443 mui.moderates[moderatesFound].community = community;