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