import { Component, linkEvent } from "inferno";
-import { Prompt } from "inferno-router";
import {
- CommunityResponse,
CommunityView,
CreateCommunity,
EditCommunity,
- UserOperation,
+ Language,
} from "lemmy-js-client";
-import { Subscription } from "rxjs";
import { i18n } from "../../i18next";
-import { UserService, WebSocketService } from "../../services";
-import {
- authField,
- capitalizeFirstLetter,
- randomStr,
- wsClient,
- wsJsonToRes,
- wsSubscribe,
- wsUserOp,
-} from "../../utils";
+import { capitalizeFirstLetter, myAuthRequired, randomStr } from "../../utils";
import { Icon, Spinner } from "../common/icon";
import { ImageUploadForm } from "../common/image-upload-form";
+import { LanguageSelect } from "../common/language-select";
import { MarkdownTextArea } from "../common/markdown-textarea";
+import NavigationPrompt from "../common/navigation-prompt";
interface CommunityFormProps {
community_view?: CommunityView; // If a community is given, that means this is an edit
+ allLanguages: Language[];
+ siteLanguages: number[];
+ communityLanguages?: number[];
onCancel?(): any;
- onCreate?(community: CommunityView): any;
- onEdit?(community: CommunityView): any;
- enableNsfw: boolean;
+ onUpsertCommunity(form: CreateCommunity | EditCommunity): void;
+ enableNsfw?: boolean;
+ loading?: boolean;
}
interface CommunityFormState {
- communityForm: CreateCommunity;
- loading: boolean;
+ form: {
+ name?: string;
+ title?: string;
+ description?: string;
+ icon?: string;
+ banner?: string;
+ nsfw?: boolean;
+ posting_restricted_to_mods?: boolean;
+ discussion_languages?: number[];
+ };
+ submitted: boolean;
}
export class CommunityForm extends Component<
CommunityFormState
> {
private id = `community-form-${randomStr()}`;
- private subscription: Subscription;
- private emptyState: CommunityFormState = {
- communityForm: {
- name: null,
- title: null,
- nsfw: false,
- icon: null,
- banner: null,
- posting_restricted_to_mods: false,
- auth: authField(false),
- },
- loading: false,
+ state: CommunityFormState = {
+ form: {},
+ submitted: false,
};
constructor(props: any, context: any) {
super(props, context);
- this.state = this.emptyState;
-
this.handleCommunityDescriptionChange =
this.handleCommunityDescriptionChange.bind(this);
this.handleBannerUpload = this.handleBannerUpload.bind(this);
this.handleBannerRemove = this.handleBannerRemove.bind(this);
- let cv = this.props.community_view;
- if (cv) {
- this.state.communityForm = {
- name: cv.community.name,
- title: cv.community.title,
- description: cv.community.description,
- nsfw: cv.community.nsfw,
- icon: cv.community.icon,
- banner: cv.community.banner,
- posting_restricted_to_mods: cv.community.posting_restricted_to_mods,
- auth: authField(),
- };
- }
+ this.handleDiscussionLanguageChange =
+ this.handleDiscussionLanguageChange.bind(this);
- this.parseMessage = this.parseMessage.bind(this);
- this.subscription = wsSubscribe(this.parseMessage);
- }
+ const cv = this.props.community_view;
- // TODO this should be checked out
- componentDidUpdate() {
- if (
- !this.state.loading &&
- (this.state.communityForm.name ||
- this.state.communityForm.title ||
- this.state.communityForm.description)
- ) {
- window.onbeforeunload = () => true;
- } else {
- window.onbeforeunload = undefined;
+ if (cv) {
+ this.state = {
+ ...this.state,
+ form: {
+ name: cv.community.name,
+ title: cv.community.title,
+ description: cv.community.description,
+ nsfw: cv.community.nsfw,
+ icon: cv.community.icon,
+ banner: cv.community.banner,
+ posting_restricted_to_mods: cv.community.posting_restricted_to_mods,
+ discussion_languages: this.props.communityLanguages,
+ },
+ };
}
}
- componentWillUnmount() {
- this.subscription.unsubscribe();
- window.onbeforeunload = null;
- }
-
render() {
return (
- <>
- <Prompt
+ <form
+ className="community-form"
+ onSubmit={linkEvent(this, this.handleCreateCommunitySubmit)}
+ >
+ <NavigationPrompt
when={
- !this.state.loading &&
- (this.state.communityForm.name ||
- this.state.communityForm.title ||
- this.state.communityForm.description)
+ !this.props.loading &&
+ !!(
+ this.state.form.name ||
+ this.state.form.title ||
+ this.state.form.description
+ ) &&
+ !this.state.submitted
}
- message={i18n.t("block_leaving")}
/>
- <form onSubmit={linkEvent(this, this.handleCreateCommunitySubmit)}>
- {!this.props.community_view && (
- <div class="form-group row">
- <label
- class="col-12 col-sm-2 col-form-label"
- htmlFor="community-name"
- >
- {i18n.t("name")}
- <span
- class="position-absolute pointer unselectable ml-2 text-muted"
- data-tippy-content={i18n.t("name_explain")}
- >
- <Icon icon="help-circle" classes="icon-inline" />
- </span>
- </label>
- <div class="col-12 col-sm-10">
- <input
- type="text"
- id="community-name"
- class="form-control"
- value={this.state.communityForm.name}
- onInput={linkEvent(this, this.handleCommunityNameChange)}
- required
- minLength={3}
- pattern="[a-z0-9_]+"
- title={i18n.t("community_reqs")}
- />
- </div>
- </div>
- )}
- <div class="form-group row">
+ {!this.props.community_view && (
+ <div className="mb-3 row">
<label
- class="col-12 col-sm-2 col-form-label"
- htmlFor="community-title"
+ className="col-12 col-sm-2 col-form-label"
+ htmlFor="community-name"
>
- {i18n.t("display_name")}
+ {i18n.t("name")}
<span
- class="position-absolute pointer unselectable ml-2 text-muted"
- data-tippy-content={i18n.t("display_name_explain")}
+ className="position-absolute pointer unselectable ms-2 text-muted"
+ data-tippy-content={i18n.t("name_explain")}
>
<Icon icon="help-circle" classes="icon-inline" />
</span>
</label>
- <div class="col-12 col-sm-10">
+ <div className="col-12 col-sm-10">
<input
type="text"
- id="community-title"
- value={this.state.communityForm.title}
- onInput={linkEvent(this, this.handleCommunityTitleChange)}
- class="form-control"
+ id="community-name"
+ className="form-control"
+ value={this.state.form.name}
+ onInput={linkEvent(this, this.handleCommunityNameChange)}
required
minLength={3}
- maxLength={100}
+ pattern="[a-z0-9_]+"
+ title={i18n.t("community_reqs")}
/>
</div>
</div>
- <div class="form-group row">
- <label class="col-12 col-sm-2">{i18n.t("icon")}</label>
- <div class="col-12 col-sm-10">
- <ImageUploadForm
- uploadTitle={i18n.t("upload_icon")}
- imageSrc={this.state.communityForm.icon}
- onUpload={this.handleIconUpload}
- onRemove={this.handleIconRemove}
- rounded
- />
- </div>
+ )}
+ <div className="mb-3 row">
+ <label
+ className="col-12 col-sm-2 col-form-label"
+ htmlFor="community-title"
+ >
+ {i18n.t("display_name")}
+ <span
+ className="position-absolute pointer unselectable ms-2 text-muted"
+ data-tippy-content={i18n.t("display_name_explain")}
+ >
+ <Icon icon="help-circle" classes="icon-inline" />
+ </span>
+ </label>
+ <div className="col-12 col-sm-10">
+ <input
+ type="text"
+ id="community-title"
+ value={this.state.form.title}
+ onInput={linkEvent(this, this.handleCommunityTitleChange)}
+ className="form-control"
+ required
+ minLength={3}
+ maxLength={100}
+ />
</div>
- <div class="form-group row">
- <label class="col-12 col-sm-2">{i18n.t("banner")}</label>
- <div class="col-12 col-sm-10">
- <ImageUploadForm
- uploadTitle={i18n.t("upload_banner")}
- imageSrc={this.state.communityForm.banner}
- onUpload={this.handleBannerUpload}
- onRemove={this.handleBannerRemove}
- />
- </div>
+ </div>
+ <div className="mb-3 row">
+ <label className="col-12 col-sm-2 col-form-label">
+ {i18n.t("icon")}
+ </label>
+ <div className="col-12 col-sm-10">
+ <ImageUploadForm
+ uploadTitle={i18n.t("upload_icon")}
+ imageSrc={this.state.form.icon}
+ onUpload={this.handleIconUpload}
+ onRemove={this.handleIconRemove}
+ rounded
+ />
</div>
- <div class="form-group row">
- <label class="col-12 col-sm-2 col-form-label" htmlFor={this.id}>
- {i18n.t("sidebar")}
- </label>
- <div class="col-12 col-sm-10">
- <MarkdownTextArea
- initialContent={this.state.communityForm.description}
- onContentChange={this.handleCommunityDescriptionChange}
- />
- </div>
+ </div>
+ <div className="mb-3 row">
+ <label className="col-12 col-sm-2 col-form-label">
+ {i18n.t("banner")}
+ </label>
+ <div className="col-12 col-sm-10">
+ <ImageUploadForm
+ uploadTitle={i18n.t("upload_banner")}
+ imageSrc={this.state.form.banner}
+ onUpload={this.handleBannerUpload}
+ onRemove={this.handleBannerRemove}
+ />
+ </div>
+ </div>
+ <div className="mb-3 row">
+ <label className="col-12 col-sm-2 col-form-label" htmlFor={this.id}>
+ {i18n.t("sidebar")}
+ </label>
+ <div className="col-12 col-sm-10">
+ <MarkdownTextArea
+ initialContent={this.state.form.description}
+ placeholder={i18n.t("description")}
+ onContentChange={this.handleCommunityDescriptionChange}
+ hideNavigationWarnings
+ allLanguages={[]}
+ siteLanguages={[]}
+ />
</div>
+ </div>
- {this.props.enableNsfw && (
- <div class="form-group row">
- <legend class="col-form-label col-sm-2 pt-0">
- {i18n.t("nsfw")}
- </legend>
- <div class="col-10">
- <div class="form-check">
- <input
- class="form-check-input position-static"
- id="community-nsfw"
- type="checkbox"
- checked={this.state.communityForm.nsfw}
- onChange={linkEvent(this, this.handleCommunityNsfwChange)}
- />
- </div>
- </div>
- </div>
- )}
- <div class="form-group row">
- <legend class="col-form-label col-6 pt-0">
- {i18n.t("only_mods_can_post_in_community")}
+ {this.props.enableNsfw && (
+ <div className="mb-3 row">
+ <legend className="col-form-label col-sm-2 pt-0">
+ {i18n.t("nsfw")}
</legend>
- <div class="col-6">
- <div class="form-check">
+ <div className="col-10">
+ <div className="form-check">
<input
- class="form-check-input position-static"
- id="community-only-mods-can-post"
+ className="form-check-input position-static"
+ id="community-nsfw"
type="checkbox"
- checked={this.state.communityForm.posting_restricted_to_mods}
- onChange={linkEvent(
- this,
- this.handleCommunityPostingRestrictedToMods
- )}
+ checked={this.state.form.nsfw}
+ onChange={linkEvent(this, this.handleCommunityNsfwChange)}
/>
</div>
</div>
</div>
- <div class="form-group row">
- <div class="col-12">
+ )}
+ <div className="mb-3 row">
+ <legend className="col-form-label col-6 pt-0">
+ {i18n.t("only_mods_can_post_in_community")}
+ </legend>
+ <div className="col-6">
+ <div className="form-check">
+ <input
+ className="form-check-input position-static"
+ id="community-only-mods-can-post"
+ type="checkbox"
+ checked={this.state.form.posting_restricted_to_mods}
+ onChange={linkEvent(
+ this,
+ this.handleCommunityPostingRestrictedToMods
+ )}
+ />
+ </div>
+ </div>
+ </div>
+ <LanguageSelect
+ allLanguages={this.props.allLanguages}
+ siteLanguages={this.props.siteLanguages}
+ showSite
+ selectedLanguageIds={this.state.form.discussion_languages}
+ multiple={true}
+ onChange={this.handleDiscussionLanguageChange}
+ />
+ <div className="mb-3 row">
+ <div className="col-12">
+ <button
+ type="submit"
+ className="btn btn-secondary me-2"
+ disabled={this.props.loading}
+ >
+ {this.props.loading ? (
+ <Spinner />
+ ) : this.props.community_view ? (
+ capitalizeFirstLetter(i18n.t("save"))
+ ) : (
+ capitalizeFirstLetter(i18n.t("create"))
+ )}
+ </button>
+ {this.props.community_view && (
<button
- type="submit"
- class="btn btn-secondary mr-2"
- disabled={this.state.loading}
+ type="button"
+ className="btn btn-secondary"
+ onClick={linkEvent(this, this.handleCancel)}
>
- {this.state.loading ? (
- <Spinner />
- ) : this.props.community_view ? (
- capitalizeFirstLetter(i18n.t("save"))
- ) : (
- capitalizeFirstLetter(i18n.t("create"))
- )}
+ {i18n.t("cancel")}
</button>
- {this.props.community_view && (
- <button
- type="button"
- class="btn btn-secondary"
- onClick={linkEvent(this, this.handleCancel)}
- >
- {i18n.t("cancel")}
- </button>
- )}
- </div>
+ )}
</div>
- </form>
- </>
+ </div>
+ </form>
);
}
handleCreateCommunitySubmit(i: CommunityForm, event: any) {
event.preventDefault();
- i.state.loading = true;
- if (i.props.community_view) {
- let form: EditCommunity = {
- ...i.state.communityForm,
- community_id: i.props.community_view.community.id,
- };
- WebSocketService.Instance.send(wsClient.editCommunity(form));
+ i.setState({ submitted: true });
+ const cForm = i.state.form;
+ const auth = myAuthRequired();
+
+ const cv = i.props.community_view;
+
+ if (cv) {
+ i.props.onUpsertCommunity({
+ community_id: cv.community.id,
+ title: cForm.title,
+ description: cForm.description,
+ icon: cForm.icon,
+ banner: cForm.banner,
+ nsfw: cForm.nsfw,
+ posting_restricted_to_mods: cForm.posting_restricted_to_mods,
+ discussion_languages: cForm.discussion_languages,
+ auth,
+ });
} else {
- WebSocketService.Instance.send(
- wsClient.createCommunity(i.state.communityForm)
- );
+ if (cForm.title && cForm.name) {
+ i.props.onUpsertCommunity({
+ name: cForm.name,
+ title: cForm.title,
+ description: cForm.description,
+ icon: cForm.icon,
+ banner: cForm.banner,
+ nsfw: cForm.nsfw,
+ posting_restricted_to_mods: cForm.posting_restricted_to_mods,
+ discussion_languages: cForm.discussion_languages,
+ auth,
+ });
+ }
}
- i.setState(i.state);
}
handleCommunityNameChange(i: CommunityForm, event: any) {
- i.state.communityForm.name = event.target.value;
- i.setState(i.state);
+ i.setState(s => ((s.form.name = event.target.value), s));
}
handleCommunityTitleChange(i: CommunityForm, event: any) {
- i.state.communityForm.title = event.target.value;
- i.setState(i.state);
+ i.setState(s => ((s.form.title = event.target.value), s));
}
handleCommunityDescriptionChange(val: string) {
- this.state.communityForm.description = val;
- this.setState(this.state);
+ this.setState(s => ((s.form.description = val), s));
}
handleCommunityNsfwChange(i: CommunityForm, event: any) {
- i.state.communityForm.nsfw = event.target.checked;
- i.setState(i.state);
+ i.setState(s => ((s.form.nsfw = event.target.checked), s));
}
handleCommunityPostingRestrictedToMods(i: CommunityForm, event: any) {
- i.state.communityForm.posting_restricted_to_mods = event.target.checked;
- i.setState(i.state);
+ i.setState(
+ s => ((s.form.posting_restricted_to_mods = event.target.checked), s)
+ );
}
handleCancel(i: CommunityForm) {
- i.props.onCancel();
+ i.props.onCancel?.();
}
handleIconUpload(url: string) {
- this.state.communityForm.icon = url;
- this.setState(this.state);
+ this.setState(s => ((s.form.icon = url), s));
}
handleIconRemove() {
- this.state.communityForm.icon = "";
- this.setState(this.state);
+ this.setState(s => ((s.form.icon = ""), s));
}
handleBannerUpload(url: string) {
- this.state.communityForm.banner = url;
- this.setState(this.state);
+ this.setState(s => ((s.form.banner = url), s));
}
handleBannerRemove() {
- this.state.communityForm.banner = "";
- this.setState(this.state);
+ this.setState(s => ((s.form.banner = ""), s));
}
- parseMessage(msg: any) {
- let op = wsUserOp(msg);
- console.log(msg);
- if (msg.error) {
- // Errors handled by top level pages
- // toast(i18n.t(msg.error), "danger");
- this.state.loading = false;
- this.setState(this.state);
- return;
- } else if (op == UserOperation.CreateCommunity) {
- let data = wsJsonToRes<CommunityResponse>(msg).data;
- this.state.loading = false;
- this.props.onCreate(data.community_view);
-
- // Update myUserInfo
- let community = data.community_view.community;
- let person = UserService.Instance.myUserInfo.local_user_view.person;
- UserService.Instance.myUserInfo.follows.push({
- community,
- follower: person,
- });
- UserService.Instance.myUserInfo.moderates.push({
- community,
- moderator: person,
- });
- } else if (op == UserOperation.EditCommunity) {
- let data = wsJsonToRes<CommunityResponse>(msg).data;
- this.state.loading = false;
- this.props.onEdit(data.community_view);
- let community = data.community_view.community;
-
- let followFound = UserService.Instance.myUserInfo.follows.findIndex(
- f => f.community.id == community.id
- );
- if (followFound) {
- UserService.Instance.myUserInfo.follows[followFound].community =
- community;
- }
-
- let moderatesFound = UserService.Instance.myUserInfo.moderates.findIndex(
- f => f.community.id == community.id
- );
- if (moderatesFound) {
- UserService.Instance.myUserInfo.moderates[moderatesFound].community =
- community;
- }
- }
+ handleDiscussionLanguageChange(val: number[]) {
+ this.setState(s => ((s.form.discussion_languages = val), s));
}
}