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