]> Untitled Git - lemmy.git/blob - ui/src/components/private-message-form.tsx
Merge remote-tracking branch 'weblate/main' into main
[lemmy.git] / ui / src / components / private-message-form.tsx
1 import { Component, linkEvent } from 'inferno';
2 import { Prompt } from 'inferno-router';
3 import { Subscription } from 'rxjs';
4 import { retryWhen, delay, take } from 'rxjs/operators';
5 import {
6   PrivateMessageForm as PrivateMessageFormI,
7   EditPrivateMessageForm,
8   PrivateMessageFormParams,
9   PrivateMessage,
10   PrivateMessageResponse,
11   UserView,
12   UserOperation,
13   UserDetailsResponse,
14   GetUserDetailsForm,
15   SortType,
16   WebSocketJsonResponse,
17 } from '../interfaces';
18 import { WebSocketService } from '../services';
19 import {
20   capitalizeFirstLetter,
21   wsJsonToRes,
22   toast,
23   setupTippy,
24 } from '../utils';
25 import { UserListing } from './user-listing';
26 import { MarkdownTextArea } from './markdown-textarea';
27 import { i18n } from '../i18next';
28 import { T } from 'inferno-i18next';
29
30 interface PrivateMessageFormProps {
31   privateMessage?: PrivateMessage; // If a pm is given, that means this is an edit
32   params?: PrivateMessageFormParams;
33   onCancel?(): any;
34   onCreate?(message: PrivateMessage): any;
35   onEdit?(message: PrivateMessage): any;
36 }
37
38 interface PrivateMessageFormState {
39   privateMessageForm: PrivateMessageFormI;
40   recipient: UserView;
41   loading: boolean;
42   previewMode: boolean;
43   showDisclaimer: boolean;
44 }
45
46 export class PrivateMessageForm extends Component<
47   PrivateMessageFormProps,
48   PrivateMessageFormState
49 > {
50   private subscription: Subscription;
51   private emptyState: PrivateMessageFormState = {
52     privateMessageForm: {
53       content: null,
54       recipient_id: null,
55     },
56     recipient: null,
57     loading: false,
58     previewMode: false,
59     showDisclaimer: false,
60   };
61
62   constructor(props: any, context: any) {
63     super(props, context);
64
65     this.state = this.emptyState;
66
67     this.handleContentChange = this.handleContentChange.bind(this);
68
69     if (this.props.privateMessage) {
70       this.state.privateMessageForm = {
71         content: this.props.privateMessage.content,
72         recipient_id: this.props.privateMessage.recipient_id,
73       };
74     }
75
76     if (this.props.params) {
77       this.state.privateMessageForm.recipient_id = this.props.params.recipient_id;
78       let form: GetUserDetailsForm = {
79         user_id: this.state.privateMessageForm.recipient_id,
80         sort: SortType[SortType.New],
81         saved_only: false,
82       };
83       WebSocketService.Instance.getUserDetails(form);
84     }
85
86     this.subscription = WebSocketService.Instance.subject
87       .pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
88       .subscribe(
89         msg => this.parseMessage(msg),
90         err => console.error(err),
91         () => console.log('complete')
92       );
93   }
94
95   componentDidMount() {
96     setupTippy();
97   }
98
99   componentDidUpdate() {
100     if (!this.state.loading && this.state.privateMessageForm.content) {
101       window.onbeforeunload = () => true;
102     } else {
103       window.onbeforeunload = undefined;
104     }
105   }
106
107   componentWillUnmount() {
108     this.subscription.unsubscribe();
109     window.onbeforeunload = null;
110   }
111
112   render() {
113     return (
114       <div>
115         <Prompt
116           when={!this.state.loading && this.state.privateMessageForm.content}
117           message={i18n.t('block_leaving')}
118         />
119         <form onSubmit={linkEvent(this, this.handlePrivateMessageSubmit)}>
120           {!this.props.privateMessage && (
121             <div class="form-group row">
122               <label class="col-sm-2 col-form-label">
123                 {capitalizeFirstLetter(i18n.t('to'))}
124               </label>
125
126               {this.state.recipient && (
127                 <div class="col-sm-10 form-control-plaintext">
128                   <UserListing
129                     user={{
130                       name: this.state.recipient.name,
131                       avatar: this.state.recipient.avatar,
132                       id: this.state.recipient.id,
133                       local: this.state.recipient.local,
134                       actor_id: this.state.recipient.actor_id,
135                     }}
136                   />
137                 </div>
138               )}
139             </div>
140           )}
141           <div class="form-group row">
142             <label class="col-sm-2 col-form-label">
143               {i18n.t('message')}
144               <span
145                 onClick={linkEvent(this, this.handleShowDisclaimer)}
146                 class="ml-2 pointer text-danger"
147                 data-tippy-content={i18n.t('disclaimer')}
148               >
149                 <svg class={`icon icon-inline`}>
150                   <use xlinkHref="#icon-alert-triangle"></use>
151                 </svg>
152               </span>
153             </label>
154             <div class="col-sm-10">
155               <MarkdownTextArea
156                 initialContent={this.state.privateMessageForm.content}
157                 onContentChange={this.handleContentChange}
158               />
159             </div>
160           </div>
161
162           {this.state.showDisclaimer && (
163             <div class="form-group row">
164               <div class="offset-sm-2 col-sm-10">
165                 <div class="alert alert-danger" role="alert">
166                   <T i18nKey="private_message_disclaimer">
167                     #
168                     <a
169                       class="alert-link"
170                       target="_blank"
171                       rel="noopener"
172                       href="https://element.io/get-started"
173                     >
174                       #
175                     </a>
176                   </T>
177                 </div>
178               </div>
179             </div>
180           )}
181           <div class="form-group row">
182             <div class="offset-sm-2 col-sm-10">
183               <button
184                 type="submit"
185                 class="btn btn-secondary mr-2"
186                 disabled={this.state.loading}
187               >
188                 {this.state.loading ? (
189                   <svg class="icon icon-spinner spin">
190                     <use xlinkHref="#icon-spinner"></use>
191                   </svg>
192                 ) : this.props.privateMessage ? (
193                   capitalizeFirstLetter(i18n.t('save'))
194                 ) : (
195                   capitalizeFirstLetter(i18n.t('send_message'))
196                 )}
197               </button>
198               {this.props.privateMessage && (
199                 <button
200                   type="button"
201                   class="btn btn-secondary"
202                   onClick={linkEvent(this, this.handleCancel)}
203                 >
204                   {i18n.t('cancel')}
205                 </button>
206               )}
207               <ul class="d-inline-block float-right list-inline mb-1 text-muted font-weight-bold">
208                 <li class="list-inline-item"></li>
209               </ul>
210             </div>
211           </div>
212         </form>
213       </div>
214     );
215   }
216
217   handlePrivateMessageSubmit(i: PrivateMessageForm, event: any) {
218     event.preventDefault();
219     if (i.props.privateMessage) {
220       let editForm: EditPrivateMessageForm = {
221         edit_id: i.props.privateMessage.id,
222         content: i.state.privateMessageForm.content,
223       };
224       WebSocketService.Instance.editPrivateMessage(editForm);
225     } else {
226       WebSocketService.Instance.createPrivateMessage(
227         i.state.privateMessageForm
228       );
229     }
230     i.state.loading = true;
231     i.setState(i.state);
232   }
233
234   handleRecipientChange(i: PrivateMessageForm, event: any) {
235     i.state.recipient = event.target.value;
236     i.setState(i.state);
237   }
238
239   handleContentChange(val: string) {
240     this.state.privateMessageForm.content = val;
241     this.setState(this.state);
242   }
243
244   handleCancel(i: PrivateMessageForm) {
245     i.props.onCancel();
246   }
247
248   handlePreviewToggle(i: PrivateMessageForm, event: any) {
249     event.preventDefault();
250     i.state.previewMode = !i.state.previewMode;
251     i.setState(i.state);
252   }
253
254   handleShowDisclaimer(i: PrivateMessageForm) {
255     i.state.showDisclaimer = !i.state.showDisclaimer;
256     i.setState(i.state);
257   }
258
259   parseMessage(msg: WebSocketJsonResponse) {
260     let res = wsJsonToRes(msg);
261     if (msg.error) {
262       toast(i18n.t(msg.error), 'danger');
263       this.state.loading = false;
264       this.setState(this.state);
265       return;
266     } else if (
267       res.op == UserOperation.EditPrivateMessage ||
268       res.op == UserOperation.DeletePrivateMessage ||
269       res.op == UserOperation.MarkPrivateMessageAsRead
270     ) {
271       let data = res.data as PrivateMessageResponse;
272       this.state.loading = false;
273       this.props.onEdit(data.message);
274     } else if (res.op == UserOperation.GetUserDetails) {
275       let data = res.data as UserDetailsResponse;
276       this.state.recipient = data.user;
277       this.state.privateMessageForm.recipient_id = data.user.id;
278       this.setState(this.state);
279     } else if (res.op == UserOperation.CreatePrivateMessage) {
280       let data = res.data as PrivateMessageResponse;
281       this.state.loading = false;
282       this.props.onCreate(data.message);
283       this.setState(this.state);
284     }
285   }
286 }