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