]> Untitled Git - lemmy.git/blob - ui/src/components/comment-form.tsx
Adding markdown buttons. Fixes #977 (#984)
[lemmy.git] / ui / src / components / comment-form.tsx
1 import { Component } from 'inferno';
2 import { Link } from 'inferno-router';
3 import { Subscription } from 'rxjs';
4 import { retryWhen, delay, take } from 'rxjs/operators';
5 import {
6   CommentNode as CommentNodeI,
7   CommentForm as CommentFormI,
8   WebSocketJsonResponse,
9   UserOperation,
10   CommentResponse,
11 } from '../interfaces';
12 import { capitalizeFirstLetter, wsJsonToRes } from '../utils';
13 import { WebSocketService, UserService } from '../services';
14 import { i18n } from '../i18next';
15 import { T } from 'inferno-i18next';
16 import { MarkdownTextArea } from './markdown-textarea';
17
18 interface CommentFormProps {
19   postId?: number;
20   node?: CommentNodeI;
21   onReplyCancel?(): any;
22   edit?: boolean;
23   disabled?: boolean;
24   focus?: boolean;
25 }
26
27 interface CommentFormState {
28   commentForm: CommentFormI;
29   buttonTitle: string;
30   finished: boolean;
31 }
32
33 export class CommentForm extends Component<CommentFormProps, CommentFormState> {
34   private subscription: Subscription;
35   private emptyState: CommentFormState = {
36     commentForm: {
37       auth: null,
38       content: null,
39       post_id: this.props.node
40         ? this.props.node.comment.post_id
41         : this.props.postId,
42       creator_id: UserService.Instance.user
43         ? UserService.Instance.user.id
44         : null,
45     },
46     buttonTitle: !this.props.node
47       ? capitalizeFirstLetter(i18n.t('post'))
48       : this.props.edit
49       ? capitalizeFirstLetter(i18n.t('save'))
50       : capitalizeFirstLetter(i18n.t('reply')),
51     finished: false,
52   };
53
54   constructor(props: any, context: any) {
55     super(props, context);
56
57     this.handleCommentSubmit = this.handleCommentSubmit.bind(this);
58     this.handleReplyCancel = this.handleReplyCancel.bind(this);
59
60     this.state = this.emptyState;
61
62     if (this.props.node) {
63       if (this.props.edit) {
64         this.state.commentForm.edit_id = this.props.node.comment.id;
65         this.state.commentForm.parent_id = this.props.node.comment.parent_id;
66         this.state.commentForm.content = this.props.node.comment.content;
67         this.state.commentForm.creator_id = this.props.node.comment.creator_id;
68       } else {
69         // A reply gets a new parent id
70         this.state.commentForm.parent_id = this.props.node.comment.id;
71       }
72     }
73
74     this.subscription = WebSocketService.Instance.subject
75       .pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
76       .subscribe(
77         msg => this.parseMessage(msg),
78         err => console.error(err),
79         () => console.log('complete')
80       );
81   }
82
83   componentWillUnmount() {
84     this.subscription.unsubscribe();
85   }
86
87   render() {
88     return (
89       <div class="mb-3">
90         {UserService.Instance.user ? (
91           <MarkdownTextArea
92             initialContent={this.state.commentForm.content}
93             buttonTitle={this.state.buttonTitle}
94             finished={this.state.finished}
95             replyType={!!this.props.node}
96             focus={this.props.focus}
97             disabled={this.props.disabled}
98             onSubmit={this.handleCommentSubmit}
99             onReplyCancel={this.handleReplyCancel}
100           />
101         ) : (
102           <div class="alert alert-light" role="alert">
103             <svg class="icon icon-inline mr-2">
104               <use xlinkHref="#icon-alert-triangle"></use>
105             </svg>
106             <T i18nKey="must_login" class="d-inline">
107               #
108               <Link class="alert-link" to="/login">
109                 #
110               </Link>
111             </T>
112           </div>
113         )}
114       </div>
115     );
116   }
117
118   handleFinished(op: UserOperation, data: CommentResponse) {
119     let isReply =
120       this.props.node !== undefined && data.comment.parent_id !== null;
121     let xor =
122       +!(data.comment.parent_id !== null) ^ +(this.props.node !== undefined);
123
124     if (
125       (data.comment.creator_id == UserService.Instance.user.id &&
126         ((op == UserOperation.CreateComment &&
127           // If its a reply, make sure parent child match
128           isReply &&
129           data.comment.parent_id == this.props.node.comment.id) ||
130           // Otherwise, check the XOR of the two
131           (!isReply && xor))) ||
132       // If its a comment edit, only check that its from your user, and that its a
133       // text edit only
134
135       (data.comment.creator_id == UserService.Instance.user.id &&
136         op == UserOperation.EditComment &&
137         data.comment.content)
138     ) {
139       this.state.finished = true;
140       this.setState(this.state);
141     }
142   }
143
144   handleCommentSubmit(val: string) {
145     this.state.commentForm.content = val;
146     if (this.props.edit) {
147       WebSocketService.Instance.editComment(this.state.commentForm);
148     } else {
149       WebSocketService.Instance.createComment(this.state.commentForm);
150     }
151     this.setState(this.state);
152   }
153
154   handleReplyCancel() {
155     this.props.onReplyCancel();
156   }
157
158   parseMessage(msg: WebSocketJsonResponse) {
159     let res = wsJsonToRes(msg);
160
161     // Only do the showing and hiding if logged in
162     if (UserService.Instance.user) {
163       if (res.op == UserOperation.CreateComment) {
164         let data = res.data as CommentResponse;
165         this.handleFinished(res.op, data);
166       } else if (res.op == UserOperation.EditComment) {
167         let data = res.data as CommentResponse;
168         this.handleFinished(res.op, data);
169       }
170     }
171   }
172 }