]> Untitled Git - lemmy.git/blob - ui/src/components/private-message-form.tsx
Merge branch 'private_messaging' into dev
[lemmy.git] / ui / src / components / private-message-form.tsx
1 import { Component, linkEvent } from 'inferno';
2 import { Link } 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 } from '../interfaces';
17 import { WebSocketService } from '../services';
18 import {
19   msgOp,
20   capitalizeFirstLetter,
21   markdownHelpUrl,
22   mdToHtml,
23   showAvatars,
24   pictshareAvatarThumbnail,
25 } from '../utils';
26 import autosize from 'autosize';
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     if (this.props.privateMessage) {
68       this.state.privateMessageForm = {
69         content: this.props.privateMessage.content,
70         recipient_id: this.props.privateMessage.recipient_id,
71       };
72     }
73
74     if (this.props.params) {
75       this.state.privateMessageForm.recipient_id = this.props.params.recipient_id;
76       let form: GetUserDetailsForm = {
77         user_id: this.state.privateMessageForm.recipient_id,
78         sort: SortType[SortType.New],
79         saved_only: false,
80       };
81       WebSocketService.Instance.getUserDetails(form);
82     }
83
84     this.subscription = WebSocketService.Instance.subject
85       .pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
86       .subscribe(
87         msg => this.parseMessage(msg),
88         err => console.error(err),
89         () => console.log('complete')
90       );
91   }
92
93   componentDidMount() {
94     autosize(document.querySelectorAll('textarea'));
95   }
96
97   componentWillUnmount() {
98     this.subscription.unsubscribe();
99   }
100
101   render() {
102     return (
103       <div>
104         <form onSubmit={linkEvent(this, this.handlePrivateMessageSubmit)}>
105           {!this.props.privateMessage && (
106             <div class="form-group row">
107               <label class="col-sm-2 col-form-label">
108                 {capitalizeFirstLetter(i18n.t('to'))}
109               </label>
110
111               {this.state.recipient && (
112                 <div class="col-sm-10 form-control-plaintext">
113                   <Link
114                     className="text-info"
115                     to={`/u/${this.state.recipient.name}`}
116                   >
117                     {this.state.recipient.avatar && showAvatars() && (
118                       <img
119                         height="32"
120                         width="32"
121                         src={pictshareAvatarThumbnail(
122                           this.state.recipient.avatar
123                         )}
124                         class="rounded-circle mr-1"
125                       />
126                     )}
127                     <span>{this.state.recipient.name}</span>
128                   </Link>
129                 </div>
130               )}
131             </div>
132           )}
133           <div class="form-group row">
134             <label class="col-sm-2 col-form-label">{i18n.t('message')}</label>
135             <div class="col-sm-10">
136               <textarea
137                 value={this.state.privateMessageForm.content}
138                 onInput={linkEvent(this, this.handleContentChange)}
139                 className={`form-control ${this.state.previewMode && 'd-none'}`}
140                 rows={4}
141                 maxLength={10000}
142               />
143               {this.state.previewMode && (
144                 <div
145                   className="md-div"
146                   dangerouslySetInnerHTML={mdToHtml(
147                     this.state.privateMessageForm.content
148                   )}
149                 />
150               )}
151
152               {this.state.privateMessageForm.content && (
153                 <button
154                   className={`mt-1 mr-2 btn btn-sm btn-secondary ${this.state
155                     .previewMode && 'active'}`}
156                   onClick={linkEvent(this, this.handlePreviewToggle)}
157                 >
158                   {i18n.t('preview')}
159                 </button>
160               )}
161               <ul class="float-right list-inline mb-1 text-muted small font-weight-bold">
162                 <li class="list-inline-item">
163                   <span
164                     onClick={linkEvent(this, this.handleShowDisclaimer)}
165                     class="pointer"
166                   >
167                     {i18n.t('disclaimer')}
168                   </span>
169                 </li>
170                 <li class="list-inline-item">
171                   <a href={markdownHelpUrl} target="_blank" class="text-muted">
172                     {i18n.t('formatting_help')}
173                   </a>
174                 </li>
175               </ul>
176             </div>
177           </div>
178
179           {this.state.showDisclaimer && (
180             <div class="form-group row">
181               <div class="col-sm-10">
182                 <div class="alert alert-danger" role="alert">
183                   <T i18nKey="private_message_disclaimer">
184                     #
185                     <a
186                       class="alert-link"
187                       target="_blank"
188                       href="https://about.riot.im/"
189                     >
190                       #
191                     </a>
192                   </T>
193                 </div>
194               </div>
195             </div>
196           )}
197           <div class="form-group row">
198             <div class="col-sm-10">
199               <button type="submit" class="btn btn-secondary mr-2">
200                 {this.state.loading ? (
201                   <svg class="icon icon-spinner spin">
202                     <use xlinkHref="#icon-spinner"></use>
203                   </svg>
204                 ) : this.props.privateMessage ? (
205                   capitalizeFirstLetter(i18n.t('save'))
206                 ) : (
207                   capitalizeFirstLetter(i18n.t('send_message'))
208                 )}
209               </button>
210               {this.props.privateMessage && (
211                 <button
212                   type="button"
213                   class="btn btn-secondary"
214                   onClick={linkEvent(this, this.handleCancel)}
215                 >
216                   {i18n.t('cancel')}
217                 </button>
218               )}
219             </div>
220           </div>
221         </form>
222       </div>
223     );
224   }
225
226   handlePrivateMessageSubmit(i: PrivateMessageForm, event: any) {
227     event.preventDefault();
228     if (i.props.privateMessage) {
229       let editForm: EditPrivateMessageForm = {
230         edit_id: i.props.privateMessage.id,
231         content: i.state.privateMessageForm.content,
232       };
233       WebSocketService.Instance.editPrivateMessage(editForm);
234     } else {
235       WebSocketService.Instance.createPrivateMessage(
236         i.state.privateMessageForm
237       );
238     }
239     i.state.loading = true;
240     i.setState(i.state);
241   }
242
243   handleRecipientChange(i: PrivateMessageForm, event: any) {
244     i.state.recipient = event.target.value;
245     i.setState(i.state);
246   }
247
248   handleContentChange(i: PrivateMessageForm, event: any) {
249     i.state.privateMessageForm.content = event.target.value;
250     i.setState(i.state);
251   }
252
253   handleCancel(i: PrivateMessageForm) {
254     i.props.onCancel();
255   }
256
257   handlePreviewToggle(i: PrivateMessageForm, event: any) {
258     event.preventDefault();
259     i.state.previewMode = !i.state.previewMode;
260     i.setState(i.state);
261   }
262
263   handleShowDisclaimer(i: PrivateMessageForm) {
264     i.state.showDisclaimer = !i.state.showDisclaimer;
265     i.setState(i.state);
266   }
267
268   parseMessage(msg: any) {
269     let op: UserOperation = msgOp(msg);
270     if (msg.error) {
271       alert(i18n.t(msg.error));
272       this.state.loading = false;
273       this.setState(this.state);
274       return;
275     } else if (op == UserOperation.EditPrivateMessage) {
276       this.state.loading = false;
277       let res: PrivateMessageResponse = msg;
278       this.props.onEdit(res.message);
279     } else if (op == UserOperation.GetUserDetails) {
280       let res: UserDetailsResponse = msg;
281       this.state.recipient = res.user;
282       this.state.privateMessageForm.recipient_id = res.user.id;
283       this.setState(this.state);
284     } else if (op == UserOperation.CreatePrivateMessage) {
285       this.state.loading = false;
286       let res: PrivateMessageResponse = msg;
287       this.props.onCreate(res.message);
288       this.setState(this.state);
289     }
290   }
291 }