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