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