]> Untitled Git - lemmy.git/blobdiff - ui/src/components/comment-form.tsx
routes.api: fix get_captcha endpoint (#1135)
[lemmy.git] / ui / src / components / comment-form.tsx
index 5239eb2c7a10660182154a1dd7f91b305ca367b7..dbd14dc76adc1ff82939d23f10714a2a60970331 100644 (file)
@@ -1,29 +1,19 @@
-import { Component, linkEvent } from 'inferno';
+import { Component } from 'inferno';
+import { Link } from 'inferno-router';
 import { Subscription } from 'rxjs';
 import { retryWhen, delay, take } from 'rxjs/operators';
-import { Prompt } from 'inferno-router';
 import {
   CommentNode as CommentNodeI,
   CommentForm as CommentFormI,
   WebSocketJsonResponse,
   UserOperation,
   CommentResponse,
-} from '../interfaces';
-import {
-  capitalizeFirstLetter,
-  mdToHtml,
-  randomStr,
-  markdownHelpUrl,
-  toast,
-  setupTribute,
-  wsJsonToRes,
-  emojiPicker,
-} from '../utils';
+} from 'lemmy-js-client';
+import { capitalizeFirstLetter, wsJsonToRes } from '../utils';
 import { WebSocketService, UserService } from '../services';
-import autosize from 'autosize';
-import Tribute from 'tributejs/src/Tribute.js';
-import emojiShortName from 'emoji-short-name';
 import { i18n } from '../i18next';
+import { T } from 'inferno-i18next';
+import { MarkdownTextArea } from './markdown-textarea';
 
 interface CommentFormProps {
   postId?: number;
@@ -31,20 +21,16 @@ interface CommentFormProps {
   onReplyCancel?(): any;
   edit?: boolean;
   disabled?: boolean;
+  focus?: boolean;
 }
 
 interface CommentFormState {
   commentForm: CommentFormI;
   buttonTitle: string;
-  previewMode: boolean;
-  loading: boolean;
-  imageLoading: boolean;
+  finished: boolean;
 }
 
 export class CommentForm extends Component<CommentFormProps, CommentFormState> {
-  private id = `comment-textarea-${randomStr()}`;
-  private formId = `comment-form-${randomStr()}`;
-  private tribute: Tribute;
   private subscription: Subscription;
   private emptyState: CommentFormState = {
     commentForm: {
@@ -60,18 +46,16 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
     buttonTitle: !this.props.node
       ? capitalizeFirstLetter(i18n.t('post'))
       : this.props.edit
-      ? capitalizeFirstLetter(i18n.t('edit'))
+      ? capitalizeFirstLetter(i18n.t('save'))
       : capitalizeFirstLetter(i18n.t('reply')),
-    previewMode: false,
-    loading: false,
-    imageLoading: false,
+    finished: false,
   };
 
   constructor(props: any, context: any) {
     super(props, context);
 
-    this.tribute = setupTribute();
-    this.setupEmojiPicker();
+    this.handleCommentSubmit = this.handleCommentSubmit.bind(this);
+    this.handleReplyCancel = this.handleReplyCancel.bind(this);
 
     this.state = this.emptyState;
 
@@ -96,17 +80,6 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
       );
   }
 
-  componentDidMount() {
-    var textarea: any = document.getElementById(this.id);
-    autosize(textarea);
-    this.tribute.attach(textarea);
-    textarea.addEventListener('tribute-replaced', () => {
-      this.state.commentForm.content = textarea.value;
-      this.setState(this.state);
-      autosize.update(textarea);
-    });
-  }
-
   componentWillUnmount() {
     this.subscription.unsubscribe();
   }
@@ -114,225 +87,47 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
   render() {
     return (
       <div class="mb-3">
-        <Prompt
-          when={this.state.commentForm.content}
-          message={i18n.t('block_leaving')}
-        />
-        <form
-          id={this.formId}
-          onSubmit={linkEvent(this, this.handleCommentSubmit)}
-        >
-          <div class="form-group row">
-            <div className={`col-sm-12`}>
-              <textarea
-                id={this.id}
-                className={`form-control ${this.state.previewMode && 'd-none'}`}
-                value={this.state.commentForm.content}
-                onInput={linkEvent(this, this.handleCommentContentChange)}
-                onPaste={linkEvent(this, this.handleImageUploadPaste)}
-                required
-                disabled={this.props.disabled}
-                rows={2}
-                maxLength={10000}
-              />
-              {this.state.previewMode && (
-                <div
-                  className="md-div"
-                  dangerouslySetInnerHTML={mdToHtml(
-                    this.state.commentForm.content
-                  )}
-                />
-              )}
-            </div>
+        {UserService.Instance.user ? (
+          <MarkdownTextArea
+            initialContent={this.state.commentForm.content}
+            buttonTitle={this.state.buttonTitle}
+            finished={this.state.finished}
+            replyType={!!this.props.node}
+            focus={this.props.focus}
+            disabled={this.props.disabled}
+            onSubmit={this.handleCommentSubmit}
+            onReplyCancel={this.handleReplyCancel}
+          />
+        ) : (
+          <div class="alert alert-light" role="alert">
+            <svg class="icon icon-inline mr-2">
+              <use xlinkHref="#icon-alert-triangle"></use>
+            </svg>
+            <T i18nKey="must_login" class="d-inline">
+              #
+              <Link class="alert-link" to="/login">
+                #
+              </Link>
+            </T>
           </div>
-          <div class="row">
-            <div class="col-sm-12">
-              <button
-                type="submit"
-                class="btn btn-sm btn-secondary mr-2"
-                disabled={this.props.disabled}
-              >
-                {this.state.loading ? (
-                  <svg class="icon icon-spinner spin">
-                    <use xlinkHref="#icon-spinner"></use>
-                  </svg>
-                ) : (
-                  <span>{this.state.buttonTitle}</span>
-                )}
-              </button>
-              {this.state.commentForm.content && (
-                <button
-                  className={`btn btn-sm mr-2 btn-secondary ${this.state
-                    .previewMode && 'active'}`}
-                  onClick={linkEvent(this, this.handlePreviewToggle)}
-                >
-                  {i18n.t('preview')}
-                </button>
-              )}
-              {this.props.node && (
-                <button
-                  type="button"
-                  class="btn btn-sm btn-secondary mr-2"
-                  onClick={linkEvent(this, this.handleReplyCancel)}
-                >
-                  {i18n.t('cancel')}
-                </button>
-              )}
-              <a
-                href={markdownHelpUrl}
-                target="_blank"
-                class="d-inline-block float-right text-muted font-weight-bold"
-                title={i18n.t('formatting_help')}
-              >
-                <svg class="icon icon-inline">
-                  <use xlinkHref="#icon-help-circle"></use>
-                </svg>
-              </a>
-              <form class="d-inline-block mr-3 float-right text-muted font-weight-bold">
-                <label
-                  htmlFor={`file-upload-${this.id}`}
-                  className={`${UserService.Instance.user && 'pointer'}`}
-                  data-tippy-content={i18n.t('upload_image')}
-                >
-                  <svg class="icon icon-inline">
-                    <use xlinkHref="#icon-image"></use>
-                  </svg>
-                </label>
-                <input
-                  id={`file-upload-${this.id}`}
-                  type="file"
-                  accept="image/*,video/*"
-                  name="file"
-                  class="d-none"
-                  disabled={!UserService.Instance.user}
-                  onChange={linkEvent(this, this.handleImageUpload)}
-                />
-              </form>
-              {this.state.imageLoading && (
-                <svg class="icon icon-spinner spin">
-                  <use xlinkHref="#icon-spinner"></use>
-                </svg>
-              )}
-              <span
-                onClick={linkEvent(this, this.handleEmojiPickerClick)}
-                class="pointer unselectable d-inline-block mr-3 float-right text-muted font-weight-bold"
-                data-tippy-content={i18n.t('emoji_picker')}
-              >
-                <svg class="icon icon-inline">
-                  <use xlinkHref="#icon-smile"></use>
-                </svg>
-              </span>
-            </div>
-          </div>
-        </form>
+        )}
       </div>
     );
   }
 
-  setupEmojiPicker() {
-    emojiPicker.on('emoji', twemojiHtmlStr => {
-      if (this.state.commentForm.content == null) {
-        this.state.commentForm.content = '';
-      }
-      var el = document.createElement('div');
-      el.innerHTML = twemojiHtmlStr;
-      let nativeUnicode = (el.childNodes[0] as HTMLElement).getAttribute('alt');
-      let shortName = `:${emojiShortName[nativeUnicode]}:`;
-      this.state.commentForm.content += shortName;
-      this.setState(this.state);
-    });
-  }
-
-  handleFinished() {
-    this.state.previewMode = false;
-    this.state.loading = false;
-    this.state.commentForm.content = '';
-    this.setState(this.state);
-    let form: any = document.getElementById(this.formId);
-    form.reset();
-    if (this.props.node) {
-      this.props.onReplyCancel();
-    }
-    autosize.update(document.querySelector('textarea'));
-    this.setState(this.state);
-  }
-
-  handleCommentSubmit(i: CommentForm, event: any) {
-    event.preventDefault();
-    if (i.props.edit) {
-      WebSocketService.Instance.editComment(i.state.commentForm);
+  handleCommentSubmit(msg: { val: string; formId: string }) {
+    this.state.commentForm.content = msg.val;
+    this.state.commentForm.form_id = msg.formId;
+    if (this.props.edit) {
+      WebSocketService.Instance.editComment(this.state.commentForm);
     } else {
-      WebSocketService.Instance.createComment(i.state.commentForm);
+      WebSocketService.Instance.createComment(this.state.commentForm);
     }
-
-    i.state.loading = true;
-    i.setState(i.state);
-  }
-
-  handleEmojiPickerClick(_i: CommentForm, event: any) {
-    emojiPicker.togglePicker(event.target);
-  }
-
-  handleCommentContentChange(i: CommentForm, event: any) {
-    i.state.commentForm.content = event.target.value;
-    i.setState(i.state);
-  }
-
-  handlePreviewToggle(i: CommentForm, event: any) {
-    event.preventDefault();
-    i.state.previewMode = !i.state.previewMode;
-    i.setState(i.state);
+    this.setState(this.state);
   }
 
-  handleReplyCancel(i: CommentForm) {
-    i.props.onReplyCancel();
-  }
-
-  handleImageUploadPaste(i: CommentForm, event: any) {
-    let image = event.clipboardData.files[0];
-    if (image) {
-      i.handleImageUpload(i, image);
-    }
-  }
-
-  handleImageUpload(i: CommentForm, event: any) {
-    let file: any;
-    if (event.target) {
-      event.preventDefault();
-      file = event.target.files[0];
-    } else {
-      file = event;
-    }
-
-    const imageUploadUrl = `/pictshare/api/upload.php`;
-    const formData = new FormData();
-    formData.append('file', file);
-
-    i.state.imageLoading = true;
-    i.setState(i.state);
-
-    fetch(imageUploadUrl, {
-      method: 'POST',
-      body: formData,
-    })
-      .then(res => res.json())
-      .then(res => {
-        let url = `${window.location.origin}/pictshare/${res.url}`;
-        let imageMarkdown =
-          res.filetype == 'mp4' ? `[vid](${url}/raw)` : `![](${url})`;
-        let content = i.state.commentForm.content;
-        content = content ? `${content}\n${imageMarkdown}` : imageMarkdown;
-        i.state.commentForm.content = content;
-        i.state.imageLoading = false;
-        i.setState(i.state);
-        let textarea: any = document.getElementById(i.id);
-        autosize.update(textarea);
-      })
-      .catch(error => {
-        i.state.imageLoading = false;
-        i.setState(i.state);
-        toast(error, 'danger');
-      });
+  handleReplyCancel() {
+    this.props.onReplyCancel();
   }
 
   parseMessage(msg: WebSocketJsonResponse) {
@@ -340,15 +135,18 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
 
     // Only do the showing and hiding if logged in
     if (UserService.Instance.user) {
-      if (res.op == UserOperation.CreateComment) {
+      if (
+        res.op == UserOperation.CreateComment ||
+        res.op == UserOperation.EditComment
+      ) {
         let data = res.data as CommentResponse;
-        if (data.comment.creator_id == UserService.Instance.user.id) {
-          this.handleFinished();
-        }
-      } else if (res.op == UserOperation.EditComment) {
-        let data = res.data as CommentResponse;
-        if (data.comment.creator_id == UserService.Instance.user.id) {
-          this.handleFinished();
+
+        // This only finishes this form, if the randomly generated form_id matches the one received
+        if (this.state.commentForm.form_id == data.form_id) {
+          this.setState({ finished: true });
+
+          // Necessary because it broke tribute for some reaso
+          this.setState({ finished: false });
         }
       }
     }