]> Untitled Git - lemmy-ui.git/blob - src/shared/components/private_message/private-message.tsx
099d87533a88b9ddef622775f9415ee6c8e7c6bd
[lemmy-ui.git] / src / shared / components / private_message / private-message.tsx
1 import { myAuthRequired } from "@utils/app";
2 import { Component, InfernoNode, linkEvent } from "inferno";
3 import {
4   CreatePrivateMessage,
5   CreatePrivateMessageReport,
6   DeletePrivateMessage,
7   EditPrivateMessage,
8   MarkPrivateMessageAsRead,
9   Person,
10   PrivateMessageView,
11 } from "lemmy-js-client";
12 import { mdToHtml } from "../../markdown";
13 import { I18NextService, UserService } from "../../services";
14 import { Icon, Spinner } from "../common/icon";
15 import { MomentTime } from "../common/moment-time";
16 import { PersonListing } from "../person/person-listing";
17 import { PrivateMessageForm } from "./private-message-form";
18
19 interface PrivateMessageState {
20   showReply: boolean;
21   showEdit: boolean;
22   collapsed: boolean;
23   viewSource: boolean;
24   showReportDialog: boolean;
25   reportReason?: string;
26   deleteLoading: boolean;
27   readLoading: boolean;
28   reportLoading: boolean;
29 }
30
31 interface PrivateMessageProps {
32   private_message_view: PrivateMessageView;
33   onDelete(form: DeletePrivateMessage): void;
34   onMarkRead(form: MarkPrivateMessageAsRead): void;
35   onReport(form: CreatePrivateMessageReport): void;
36   onCreate(form: CreatePrivateMessage): void;
37   onEdit(form: EditPrivateMessage): void;
38 }
39
40 export class PrivateMessage extends Component<
41   PrivateMessageProps,
42   PrivateMessageState
43 > {
44   state: PrivateMessageState = {
45     showReply: false,
46     showEdit: false,
47     collapsed: false,
48     viewSource: false,
49     showReportDialog: false,
50     deleteLoading: false,
51     readLoading: false,
52     reportLoading: false,
53   };
54
55   constructor(props: any, context: any) {
56     super(props, context);
57     this.handleReplyCancel = this.handleReplyCancel.bind(this);
58   }
59
60   get mine(): boolean {
61     return (
62       UserService.Instance.myUserInfo?.local_user_view.person.id ===
63       this.props.private_message_view.creator.id
64     );
65   }
66
67   componentWillReceiveProps(
68     nextProps: Readonly<{ children?: InfernoNode } & PrivateMessageProps>
69   ): void {
70     if (this.props !== nextProps) {
71       this.setState({
72         showReply: false,
73         showEdit: false,
74         collapsed: false,
75         viewSource: false,
76         showReportDialog: false,
77         deleteLoading: false,
78         readLoading: false,
79         reportLoading: false,
80       });
81     }
82   }
83
84   render() {
85     const message_view = this.props.private_message_view;
86     const otherPerson: Person = this.mine
87       ? message_view.recipient
88       : message_view.creator;
89
90     return (
91       <div className="private-message border-top border-light">
92         <div>
93           <ul className="list-inline mb-0 text-muted small">
94             {/* TODO refactor this */}
95             <li className="list-inline-item">
96               {this.mine
97                 ? I18NextService.i18n.t("to")
98                 : I18NextService.i18n.t("from")}
99             </li>
100             <li className="list-inline-item">
101               <PersonListing person={otherPerson} />
102             </li>
103             <li className="list-inline-item">
104               <span>
105                 <MomentTime
106                   published={message_view.private_message.published}
107                   updated={message_view.private_message.updated}
108                 />
109               </span>
110             </li>
111             <li className="list-inline-item">
112               <button
113                 type="button"
114                 className="pointer text-monospace p-0 bg-transparent border-0 d-block"
115                 onClick={linkEvent(this, this.handleMessageCollapse)}
116               >
117                 {this.state.collapsed ? (
118                   <Icon icon="plus-square" />
119                 ) : (
120                   <Icon icon="minus-square" />
121                 )}
122               </button>
123             </li>
124           </ul>
125           {this.state.showEdit && (
126             <PrivateMessageForm
127               recipient={otherPerson}
128               privateMessageView={message_view}
129               onEdit={this.props.onEdit}
130               onCancel={this.handleReplyCancel}
131             />
132           )}
133           {!this.state.showEdit && !this.state.collapsed && (
134             <div>
135               {this.state.viewSource ? (
136                 <pre>{this.messageUnlessRemoved}</pre>
137               ) : (
138                 <div
139                   className="md-div"
140                   dangerouslySetInnerHTML={mdToHtml(this.messageUnlessRemoved)}
141                 />
142               )}
143               <ul className="list-inline mb-0 text-muted fw-bold">
144                 {!this.mine && (
145                   <>
146                     <li className="list-inline-item">
147                       <button
148                         type="button"
149                         className="btn btn-link btn-animate text-muted"
150                         onClick={linkEvent(this, this.handleMarkRead)}
151                         data-tippy-content={
152                           message_view.private_message.read
153                             ? I18NextService.i18n.t("mark_as_unread")
154                             : I18NextService.i18n.t("mark_as_read")
155                         }
156                         aria-label={
157                           message_view.private_message.read
158                             ? I18NextService.i18n.t("mark_as_unread")
159                             : I18NextService.i18n.t("mark_as_read")
160                         }
161                       >
162                         {this.state.readLoading ? (
163                           <Spinner />
164                         ) : (
165                           <Icon
166                             icon="check"
167                             classes={`icon-inline ${
168                               message_view.private_message.read &&
169                               "text-success"
170                             }`}
171                           />
172                         )}
173                       </button>
174                     </li>
175                     <li className="list-inline-item">{this.reportButton}</li>
176                     <li className="list-inline-item">
177                       <button
178                         type="button"
179                         className="btn btn-link btn-animate text-muted"
180                         onClick={linkEvent(this, this.handleReplyClick)}
181                         data-tippy-content={I18NextService.i18n.t("reply")}
182                         aria-label={I18NextService.i18n.t("reply")}
183                       >
184                         <Icon icon="reply1" classes="icon-inline" />
185                       </button>
186                     </li>
187                   </>
188                 )}
189                 {this.mine && (
190                   <>
191                     <li className="list-inline-item">
192                       <button
193                         type="button"
194                         className="btn btn-link btn-animate text-muted"
195                         onClick={linkEvent(this, this.handleEditClick)}
196                         data-tippy-content={I18NextService.i18n.t("edit")}
197                         aria-label={I18NextService.i18n.t("edit")}
198                       >
199                         <Icon icon="edit" classes="icon-inline" />
200                       </button>
201                     </li>
202                     <li className="list-inline-item">
203                       <button
204                         type="button"
205                         className="btn btn-link btn-animate text-muted"
206                         onClick={linkEvent(this, this.handleDeleteClick)}
207                         data-tippy-content={
208                           !message_view.private_message.deleted
209                             ? I18NextService.i18n.t("delete")
210                             : I18NextService.i18n.t("restore")
211                         }
212                         aria-label={
213                           !message_view.private_message.deleted
214                             ? I18NextService.i18n.t("delete")
215                             : I18NextService.i18n.t("restore")
216                         }
217                       >
218                         {this.state.deleteLoading ? (
219                           <Spinner />
220                         ) : (
221                           <Icon
222                             icon="trash"
223                             classes={`icon-inline ${
224                               message_view.private_message.deleted &&
225                               "text-danger"
226                             }`}
227                           />
228                         )}
229                       </button>
230                     </li>
231                   </>
232                 )}
233                 <li className="list-inline-item">
234                   <button
235                     type="button"
236                     className="btn btn-link btn-animate text-muted"
237                     onClick={linkEvent(this, this.handleViewSource)}
238                     data-tippy-content={I18NextService.i18n.t("view_source")}
239                     aria-label={I18NextService.i18n.t("view_source")}
240                   >
241                     <Icon
242                       icon="file-text"
243                       classes={`icon-inline ${
244                         this.state.viewSource && "text-success"
245                       }`}
246                     />
247                   </button>
248                 </li>
249               </ul>
250             </div>
251           )}
252         </div>
253         {this.state.showReportDialog && (
254           <form
255             className="form-inline"
256             onSubmit={linkEvent(this, this.handleReportSubmit)}
257           >
258             <label className="visually-hidden" htmlFor="pm-report-reason">
259               {I18NextService.i18n.t("reason")}
260             </label>
261             <input
262               type="text"
263               id="pm-report-reason"
264               className="form-control me-2"
265               placeholder={I18NextService.i18n.t("reason")}
266               required
267               value={this.state.reportReason}
268               onInput={linkEvent(this, this.handleReportReasonChange)}
269             />
270             <button
271               type="submit"
272               className="btn btn-secondary"
273               aria-label={I18NextService.i18n.t("create_report")}
274             >
275               {this.state.reportLoading ? (
276                 <Spinner />
277               ) : (
278                 I18NextService.i18n.t("create_report")
279               )}
280             </button>
281           </form>
282         )}
283         {this.state.showReply && (
284           <div className="row">
285             <div className="col-sm-6">
286               <PrivateMessageForm
287                 replyType={true}
288                 recipient={otherPerson}
289                 onCreate={this.props.onCreate}
290                 onCancel={this.handleReplyCancel}
291               />
292             </div>
293           </div>
294         )}
295         {/* A collapsed clearfix */}
296         {this.state.collapsed && <div className="row col-12"></div>}
297       </div>
298     );
299   }
300
301   get reportButton() {
302     return (
303       <button
304         type="button"
305         className="btn btn-link btn-animate text-muted py-0"
306         onClick={linkEvent(this, this.handleShowReportDialog)}
307         data-tippy-content={I18NextService.i18n.t("show_report_dialog")}
308         aria-label={I18NextService.i18n.t("show_report_dialog")}
309       >
310         <Icon icon="flag" inline />
311       </button>
312     );
313   }
314
315   get messageUnlessRemoved(): string {
316     const message = this.props.private_message_view.private_message;
317     return message.deleted
318       ? `*${I18NextService.i18n.t("deleted")}*`
319       : message.content;
320   }
321
322   handleReplyClick(i: PrivateMessage) {
323     i.setState({ showReply: true });
324   }
325
326   handleEditClick(i: PrivateMessage) {
327     i.setState({ showEdit: true });
328     i.setState(i.state);
329   }
330
331   handleDeleteClick(i: PrivateMessage) {
332     i.setState({ deleteLoading: true });
333     i.props.onDelete({
334       private_message_id: i.props.private_message_view.private_message.id,
335       deleted: !i.props.private_message_view.private_message.deleted,
336       auth: myAuthRequired(),
337     });
338   }
339
340   handleReplyCancel() {
341     this.setState({ showReply: false, showEdit: false });
342   }
343
344   handleMarkRead(i: PrivateMessage) {
345     i.setState({ readLoading: true });
346     i.props.onMarkRead({
347       private_message_id: i.props.private_message_view.private_message.id,
348       read: !i.props.private_message_view.private_message.read,
349       auth: myAuthRequired(),
350     });
351   }
352
353   handleMessageCollapse(i: PrivateMessage) {
354     i.setState({ collapsed: !i.state.collapsed });
355   }
356
357   handleViewSource(i: PrivateMessage) {
358     i.setState({ viewSource: !i.state.viewSource });
359   }
360
361   handleShowReportDialog(i: PrivateMessage) {
362     i.setState({ showReportDialog: !i.state.showReportDialog });
363   }
364
365   handleReportReasonChange(i: PrivateMessage, event: any) {
366     i.setState({ reportReason: event.target.value });
367   }
368
369   handleReportSubmit(i: PrivateMessage, event: any) {
370     event.preventDefault();
371     i.setState({ reportLoading: true });
372     i.props.onReport({
373       private_message_id: i.props.private_message_view.private_message.id,
374       reason: i.state.reportReason ?? "",
375       auth: myAuthRequired(),
376     });
377   }
378 }