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