]> Untitled Git - lemmy-ui.git/blob - src/shared/components/private_message/private-message-form.tsx
Merge branch 'LemmyNet:main' into multiple-images-upload
[lemmy-ui.git] / src / shared / components / private_message / private-message-form.tsx
1 import { None, Option, Some } from "@sniptt/monads";
2 import { Component, linkEvent } from "inferno";
3 import { T } from "inferno-i18next-dess";
4 import { Prompt } from "inferno-router";
5 import {
6   CreatePrivateMessage,
7   EditPrivateMessage,
8   PersonSafe,
9   PrivateMessageResponse,
10   PrivateMessageView,
11   UserOperation,
12   wsJsonToRes,
13   wsUserOp,
14 } from "lemmy-js-client";
15 import { Subscription } from "rxjs";
16 import { i18n } from "../../i18next";
17 import { WebSocketService } from "../../services";
18 import {
19   auth,
20   capitalizeFirstLetter,
21   isBrowser,
22   relTags,
23   setupTippy,
24   toast,
25   wsClient,
26   wsSubscribe,
27 } from "../../utils";
28 import { Icon, Spinner } from "../common/icon";
29 import { MarkdownTextArea } from "../common/markdown-textarea";
30 import { PersonListing } from "../person/person-listing";
31
32 interface PrivateMessageFormProps {
33   recipient: PersonSafe;
34   privateMessageView: Option<PrivateMessageView>; // If a pm is given, that means this is an edit
35   onCancel?(): any;
36   onCreate?(message: PrivateMessageView): any;
37   onEdit?(message: PrivateMessageView): any;
38 }
39
40 interface PrivateMessageFormState {
41   privateMessageForm: CreatePrivateMessage;
42   loading: boolean;
43   previewMode: boolean;
44   showDisclaimer: boolean;
45 }
46
47 export class PrivateMessageForm extends Component<
48   PrivateMessageFormProps,
49   PrivateMessageFormState
50 > {
51   private subscription: Subscription;
52   private emptyState: PrivateMessageFormState = {
53     privateMessageForm: new CreatePrivateMessage({
54       content: null,
55       recipient_id: this.props.recipient.id,
56       auth: auth().unwrap(),
57     }),
58     loading: false,
59     previewMode: false,
60     showDisclaimer: false,
61   };
62
63   constructor(props: any, context: any) {
64     super(props, context);
65
66     this.state = this.emptyState;
67
68     this.handleContentChange = this.handleContentChange.bind(this);
69
70     this.parseMessage = this.parseMessage.bind(this);
71     this.subscription = wsSubscribe(this.parseMessage);
72
73     // Its an edit
74     if (this.props.privateMessageView.isSome()) {
75       this.state.privateMessageForm.content =
76         this.props.privateMessageView.unwrap().private_message.content;
77     }
78   }
79
80   componentDidMount() {
81     setupTippy();
82   }
83
84   componentDidUpdate() {
85     if (!this.state.loading && this.state.privateMessageForm.content) {
86       window.onbeforeunload = () => true;
87     } else {
88       window.onbeforeunload = undefined;
89     }
90   }
91
92   componentWillUnmount() {
93     if (isBrowser()) {
94       this.subscription.unsubscribe();
95       window.onbeforeunload = null;
96     }
97   }
98
99   render() {
100     return (
101       <div>
102         <Prompt
103           when={!this.state.loading && this.state.privateMessageForm.content}
104           message={i18n.t("block_leaving")}
105         />
106         <form onSubmit={linkEvent(this, this.handlePrivateMessageSubmit)}>
107           {this.props.privateMessageView.isNone() && (
108             <div className="form-group row">
109               <label className="col-sm-2 col-form-label">
110                 {capitalizeFirstLetter(i18n.t("to"))}
111               </label>
112
113               <div className="col-sm-10 form-control-plaintext">
114                 <PersonListing person={this.props.recipient} />
115               </div>
116             </div>
117           )}
118           <div className="form-group row">
119             <label className="col-sm-2 col-form-label">
120               {i18n.t("message")}
121               <button
122                 className="btn btn-link text-warning d-inline-block"
123                 onClick={linkEvent(this, this.handleShowDisclaimer)}
124                 data-tippy-content={i18n.t("private_message_disclaimer")}
125                 aria-label={i18n.t("private_message_disclaimer")}
126               >
127                 <Icon icon="alert-triangle" classes="icon-inline" />
128               </button>
129             </label>
130             <div className="col-sm-10">
131               <MarkdownTextArea
132                 initialContent={Some(this.state.privateMessageForm.content)}
133                 initialLanguageId={None}
134                 placeholder={None}
135                 buttonTitle={None}
136                 maxLength={None}
137                 onContentChange={this.handleContentChange}
138                 allLanguages={[]}
139               />
140             </div>
141           </div>
142
143           {this.state.showDisclaimer && (
144             <div className="form-group row">
145               <div className="offset-sm-2 col-sm-10">
146                 <div className="alert alert-danger" role="alert">
147                   <T i18nKey="private_message_disclaimer">
148                     #
149                     <a
150                       className="alert-link"
151                       rel={relTags}
152                       href="https://element.io/get-started"
153                     >
154                       #
155                     </a>
156                   </T>
157                 </div>
158               </div>
159             </div>
160           )}
161           <div className="form-group row">
162             <div className="offset-sm-2 col-sm-10">
163               <button
164                 type="submit"
165                 className="btn btn-secondary mr-2"
166                 disabled={this.state.loading}
167               >
168                 {this.state.loading ? (
169                   <Spinner />
170                 ) : this.props.privateMessageView.isSome() ? (
171                   capitalizeFirstLetter(i18n.t("save"))
172                 ) : (
173                   capitalizeFirstLetter(i18n.t("send_message"))
174                 )}
175               </button>
176               {this.props.privateMessageView.isSome() && (
177                 <button
178                   type="button"
179                   className="btn btn-secondary"
180                   onClick={linkEvent(this, this.handleCancel)}
181                 >
182                   {i18n.t("cancel")}
183                 </button>
184               )}
185               <ul className="d-inline-block float-right list-inline mb-1 text-muted font-weight-bold">
186                 <li className="list-inline-item"></li>
187               </ul>
188             </div>
189           </div>
190         </form>
191       </div>
192     );
193   }
194
195   handlePrivateMessageSubmit(i: PrivateMessageForm, event: any) {
196     event.preventDefault();
197     i.props.privateMessageView.match({
198       some: pm => {
199         let form = new EditPrivateMessage({
200           private_message_id: pm.private_message.id,
201           content: i.state.privateMessageForm.content,
202           auth: auth().unwrap(),
203         });
204         WebSocketService.Instance.send(wsClient.editPrivateMessage(form));
205       },
206       none: WebSocketService.Instance.send(
207         wsClient.createPrivateMessage(i.state.privateMessageForm)
208       ),
209     });
210     i.setState({ loading: true });
211   }
212
213   handleContentChange(val: string) {
214     this.setState(s => ((s.privateMessageForm.content = val), s));
215   }
216
217   handleCancel(i: PrivateMessageForm) {
218     i.props.onCancel();
219   }
220
221   handlePreviewToggle(i: PrivateMessageForm, event: any) {
222     event.preventDefault();
223     i.setState({ previewMode: !i.state.previewMode });
224   }
225
226   handleShowDisclaimer(i: PrivateMessageForm) {
227     i.setState({ showDisclaimer: !i.state.showDisclaimer });
228   }
229
230   parseMessage(msg: any) {
231     let op = wsUserOp(msg);
232     console.log(msg);
233     if (msg.error) {
234       toast(i18n.t(msg.error), "danger");
235       this.setState({ loading: false });
236       return;
237     } else if (
238       op == UserOperation.EditPrivateMessage ||
239       op == UserOperation.DeletePrivateMessage ||
240       op == UserOperation.MarkPrivateMessageAsRead
241     ) {
242       let data = wsJsonToRes<PrivateMessageResponse>(
243         msg,
244         PrivateMessageResponse
245       );
246       this.setState({ loading: false });
247       this.props.onEdit(data.private_message_view);
248     } else if (op == UserOperation.CreatePrivateMessage) {
249       let data = wsJsonToRes<PrivateMessageResponse>(
250         msg,
251         PrivateMessageResponse
252       );
253       this.props.onCreate(data.private_message_view);
254     }
255   }
256 }