]> Untitled Git - lemmy-ui.git/blob - src/shared/components/private_message/private-message.tsx
component classes v2
[lemmy-ui.git] / src / shared / components / private_message / private-message.tsx
1 import { Component, InfernoNode, linkEvent } from "inferno";
2 import {
3   CreatePrivateMessage,
4   CreatePrivateMessageReport,
5   DeletePrivateMessage,
6   EditPrivateMessage,
7   MarkPrivateMessageAsRead,
8   Person,
9   PrivateMessageView,
10 } from "lemmy-js-client";
11 import { i18n } from "../../i18next";
12 import { UserService } from "../../services";
13 import { mdToHtml, myAuthRequired } from "../../utils";
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 ? i18n.t("to") : i18n.t("from")}
97             </li>
98             <li className="list-inline-item">
99               <PersonListing person={otherPerson} />
100             </li>
101             <li className="list-inline-item">
102               <span>
103                 <MomentTime
104                   published={message_view.private_message.published}
105                   updated={message_view.private_message.updated}
106                 />
107               </span>
108             </li>
109             <li className="list-inline-item">
110               <div
111                 role="button"
112                 className="pointer text-monospace"
113                 onClick={linkEvent(this, this.handleMessageCollapse)}
114               >
115                 {this.state.collapsed ? (
116                   <Icon icon="plus-square" classes="icon-inline" />
117                 ) : (
118                   <Icon icon="minus-square" classes="icon-inline" />
119                 )}
120               </div>
121             </li>
122           </ul>
123           {this.state.showEdit && (
124             <PrivateMessageForm
125               recipient={otherPerson}
126               privateMessageView={message_view}
127               onEdit={this.props.onEdit}
128               onCancel={this.handleReplyCancel}
129             />
130           )}
131           {!this.state.showEdit && !this.state.collapsed && (
132             <div>
133               {this.state.viewSource ? (
134                 <pre>{this.messageUnlessRemoved}</pre>
135               ) : (
136                 <div
137                   className="md-div"
138                   dangerouslySetInnerHTML={mdToHtml(this.messageUnlessRemoved)}
139                 />
140               )}
141               <ul className="list-inline mb-0 text-muted font-weight-bold">
142                 {!this.mine && (
143                   <>
144                     <li className="list-inline-item">
145                       <button
146                         className="btn btn-link btn-animate text-muted"
147                         onClick={linkEvent(this, this.handleMarkRead)}
148                         data-tippy-content={
149                           message_view.private_message.read
150                             ? i18n.t("mark_as_unread")
151                             : i18n.t("mark_as_read")
152                         }
153                         aria-label={
154                           message_view.private_message.read
155                             ? i18n.t("mark_as_unread")
156                             : i18n.t("mark_as_read")
157                         }
158                       >
159                         {this.state.readLoading ? (
160                           <Spinner />
161                         ) : (
162                           <Icon
163                             icon="check"
164                             classes={`icon-inline ${
165                               message_view.private_message.read &&
166                               "text-success"
167                             }`}
168                           />
169                         )}
170                       </button>
171                     </li>
172                     <li className="list-inline-item">{this.reportButton}</li>
173                     <li className="list-inline-item">
174                       <button
175                         className="btn btn-link btn-animate text-muted"
176                         onClick={linkEvent(this, this.handleReplyClick)}
177                         data-tippy-content={i18n.t("reply")}
178                         aria-label={i18n.t("reply")}
179                       >
180                         <Icon icon="reply1" classes="icon-inline" />
181                       </button>
182                     </li>
183                   </>
184                 )}
185                 {this.mine && (
186                   <>
187                     <li className="list-inline-item">
188                       <button
189                         className="btn btn-link btn-animate text-muted"
190                         onClick={linkEvent(this, this.handleEditClick)}
191                         data-tippy-content={i18n.t("edit")}
192                         aria-label={i18n.t("edit")}
193                       >
194                         <Icon icon="edit" classes="icon-inline" />
195                       </button>
196                     </li>
197                     <li className="list-inline-item">
198                       <button
199                         className="btn btn-link btn-animate text-muted"
200                         onClick={linkEvent(this, this.handleDeleteClick)}
201                         data-tippy-content={
202                           !message_view.private_message.deleted
203                             ? i18n.t("delete")
204                             : i18n.t("restore")
205                         }
206                         aria-label={
207                           !message_view.private_message.deleted
208                             ? i18n.t("delete")
209                             : i18n.t("restore")
210                         }
211                       >
212                         {this.state.deleteLoading ? (
213                           <Spinner />
214                         ) : (
215                           <Icon
216                             icon="trash"
217                             classes={`icon-inline ${
218                               message_view.private_message.deleted &&
219                               "text-danger"
220                             }`}
221                           />
222                         )}
223                       </button>
224                     </li>
225                   </>
226                 )}
227                 <li className="list-inline-item">
228                   <button
229                     className="btn btn-link btn-animate text-muted"
230                     onClick={linkEvent(this, this.handleViewSource)}
231                     data-tippy-content={i18n.t("view_source")}
232                     aria-label={i18n.t("view_source")}
233                   >
234                     <Icon
235                       icon="file-text"
236                       classes={`icon-inline ${
237                         this.state.viewSource && "text-success"
238                       }`}
239                     />
240                   </button>
241                 </li>
242               </ul>
243             </div>
244           )}
245         </div>
246         {this.state.showReportDialog && (
247           <form
248             className="form-inline"
249             onSubmit={linkEvent(this, this.handleReportSubmit)}
250           >
251             <label className="visually-hidden" htmlFor="pm-report-reason">
252               {i18n.t("reason")}
253             </label>
254             <input
255               type="text"
256               id="pm-report-reason"
257               className="form-control me-2"
258               placeholder={i18n.t("reason")}
259               required
260               value={this.state.reportReason}
261               onInput={linkEvent(this, this.handleReportReasonChange)}
262             />
263             <button
264               type="submit"
265               className="btn btn-secondary"
266               aria-label={i18n.t("create_report")}
267             >
268               {this.state.reportLoading ? <Spinner /> : i18n.t("create_report")}
269             </button>
270           </form>
271         )}
272         {this.state.showReply && (
273           <PrivateMessageForm
274             recipient={otherPerson}
275             onCreate={this.props.onCreate}
276           />
277         )}
278         {/* A collapsed clearfix */}
279         {this.state.collapsed && <div className="row col-12"></div>}
280       </div>
281     );
282   }
283
284   get reportButton() {
285     return (
286       <button
287         className="btn btn-link btn-animate text-muted py-0"
288         onClick={linkEvent(this, this.handleShowReportDialog)}
289         data-tippy-content={i18n.t("show_report_dialog")}
290         aria-label={i18n.t("show_report_dialog")}
291       >
292         <Icon icon="flag" inline />
293       </button>
294     );
295   }
296
297   get messageUnlessRemoved(): string {
298     const message = this.props.private_message_view.private_message;
299     return message.deleted ? `*${i18n.t("deleted")}*` : message.content;
300   }
301
302   handleReplyClick(i: PrivateMessage) {
303     i.setState({ showReply: true });
304   }
305
306   handleEditClick(i: PrivateMessage) {
307     i.setState({ showEdit: true });
308     i.setState(i.state);
309   }
310
311   handleDeleteClick(i: PrivateMessage) {
312     i.setState({ deleteLoading: true });
313     i.props.onDelete({
314       private_message_id: i.props.private_message_view.private_message.id,
315       deleted: !i.props.private_message_view.private_message.deleted,
316       auth: myAuthRequired(),
317     });
318   }
319
320   handleReplyCancel() {
321     this.setState({ showReply: false, showEdit: false });
322   }
323
324   handleMarkRead(i: PrivateMessage) {
325     i.setState({ readLoading: true });
326     i.props.onMarkRead({
327       private_message_id: i.props.private_message_view.private_message.id,
328       read: !i.props.private_message_view.private_message.read,
329       auth: myAuthRequired(),
330     });
331   }
332
333   handleMessageCollapse(i: PrivateMessage) {
334     i.setState({ collapsed: !i.state.collapsed });
335   }
336
337   handleViewSource(i: PrivateMessage) {
338     i.setState({ viewSource: !i.state.viewSource });
339   }
340
341   handleShowReportDialog(i: PrivateMessage) {
342     i.setState({ showReportDialog: !i.state.showReportDialog });
343   }
344
345   handleReportReasonChange(i: PrivateMessage, event: any) {
346     i.setState({ reportReason: event.target.value });
347   }
348
349   handleReportSubmit(i: PrivateMessage, event: any) {
350     event.preventDefault();
351     i.setState({ reportLoading: true });
352     i.props.onReport({
353       private_message_id: i.props.private_message_view.private_message.id,
354       reason: i.state.reportReason ?? "",
355       auth: myAuthRequired(),
356     });
357   }
358 }