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