<commit_before>import React from 'react';
import { connect } from 'react-redux';
import Textarea from 'react-textarea-autosize';

import { addComment, updateHighlightedComments } from '../../redux/action-creators';
import Icon from './icon';
import Throbber from './throbber';
import { getDraftCA, setDraftCA } from '../../utils/drafts';

class CommentCreateForm extends React.Component {
  bindTextarea = (textarea) => {
    this._textarea = textarea;
    this.props.bindTextarea(this._textarea);
  };

  typedArrows = []; // Array of arrows (^^^) lengths, that user typing in the textarea

  startCommenting = () => {
    if (!this.props.post.isCommenting) {
      this.props.toggleCommenting();
    }
  };

  cancelCommenting = () => {
    if (!this._textarea.value || confirm('Discard changes and close the form?')) {
      this.props.toggleCommenting();
      this.props.updateHighlightedComments();
      this.typedArrows = [];
      setDraftCA(this.props.post.id, null);
    }
  };

  handleKeyDown = (event) => {
    if (event.key === 'Enter' && !event.shiftKey) {
      event.preventDefault();
      setTimeout(this.saveComment, 0);
    }
  };

  handleKeyUp = (event) => {
    if (event.key === 'Escape') {
      this.cancelCommenting();
    }
  };

  handleChangeText = () => {
    const arrowsFound = this._textarea.value.match(/\^+/g);
    const arrows = (arrowsFound ? arrowsFound.map(a => a.length) : []);

    if (this.typedArrows.length !== arrows.length || !this.typedArrows.every((v, i) => (v === arrows[i]))) { // just comparing two arrays
      this.typedArrows = arrows;
      if (arrows.length > 0) {
        this.props.updateHighlightedComments({ reason: 'hover-arrows', postId: this.props.post.id, baseCommentId: null, arrows });
      } else {
        this.props.updateHighlightedComments();
      }
    }

    setDraftCA(this.props.post.id, this._textarea.value);
  };

  saveComment = () => {
    if (!this.props.post.isSavingComment) {
      this.props.addComment(this.props.post.id, this._textarea.value);
      this.props.updateHighlightedComments();
      this.typedArrows = [];
    }
  };

  componentWillReceiveProps(newProps) {
    // If it was successful saving, clear the form
    const isSavingFinished = this.props.post.isSavingComment && !newProps.post.isSavingComment;
    const isSavingFailed = newProps.post.commentError;
    if (isSavingFinished && !isSavingFailed) {
      this._textarea.value = '';
    }
  }

  render() {
    const writingComment = this.props.post.isCommenting;
    const singlePost = this.props.isSinglePost;
    const manyComments = this.props.otherCommentsNumber > 2 && !this.props.post.omittedComments /* TODO: && user_is_signed_in */;
    const draft = getDraftCA(this.props.post.id);

    if (!writingComment && !singlePost && !draft && !manyComments) {
      return false;
    }

    return (
      <div className="comment">
        <a className="comment-icon comment-icon-add" onClick={this.startCommenting}>
          <Icon name="comment-plus"/>
        </a>

        {writingComment ? (
          <div className="comment-body">
            <Textarea
              inputRef={this.bindTextarea}
              className="form-control comment-textarea"
              defaultValue={draft ?? ''}
              autoFocus={true}
              onKeyDown={this.handleKeyDown}
              onKeyUp={this.handleKeyUp}
              onChange={this.handleChangeText}
              minRows={2}
              maxRows={10}
              maxLength="1500"/>

            <button className="btn btn-default btn-xs comment-post" onClick={this.saveComment}>Comment</button>

            <a className="comment-cancel" onClick={this.cancelCommenting}>Cancel</a>

            {this.props.post.isSavingComment ? (
              <Throbber name="comment-edit"/>
            ) : this.props.post.commentError ? (
              <div className="comment-error alert alert-danger" role="alert">
                Comment has not been saved. Server response: "{this.props.post.commentError}"
              </div>
            ) : false}
          </div>
        ) : (singlePost || draft) ? (
          <div className="comment-body">
            <textarea
              className="form-control comment-textarea"
              rows={2}
              placeholder={draft}
              onFocus={this.startCommenting}/>
          </div>
        ) : <>
          <a className="add-comment-link" onClick={this.startCommenting}>Add comment</a>

          {this.props.post.commentsDisabled && this.props.post.canIModerate && (
            <i> - disabled for others</i>
          )}
        </>}
      </div>
    );
  }
}

function mapDispatchToProps(dispatch) {
  return {
    addComment: (...args) => dispatch(addComment(...args)),
    updateHighlightedComments: (...args) => dispatch(updateHighlightedComments(...args))
  };
}

export default connect(null, mapDispatchToProps)(CommentCreateForm);
<commit_msg>Refactor CommentCreateForm (class -> fn w/ hooks)<commit_after>import React, { useCallback, useRef } from 'react';
import { useDispatch } from 'react-redux';
import Textarea from 'react-textarea-autosize';

import { addComment, updateHighlightedComments } from '../../redux/action-creators';
import Icon from './icon';
import Throbber from './throbber';
import { getDraftCA, setDraftCA } from '../../utils/drafts';

const CommentCreateForm = ({ post, isSinglePost, otherCommentsNumber, toggleCommenting, bindTextarea }) => {
  const dispatch = useDispatch();

  const textarea = useRef({}); // Textarea DOM element
  const typedArrows = useRef([]); // Array of arrows (^^^) lengths, that user typing in the textarea

  const textareaCallbackRef = useCallback(textareaElement => {
    textarea.current = textareaElement;
    bindTextarea(textareaElement);
  }, [bindTextarea]);

  const startCommenting = useCallback(() => {
    if (!post.isCommenting) {
      toggleCommenting();
    }
  }, [post.isCommenting, toggleCommenting]);

  const cancelCommenting = useCallback(() => {
    if (!textarea.current.value || confirm('Discard changes and close the form?')) {
      toggleCommenting();
      dispatch(updateHighlightedComments());
      typedArrows.current = [];
      setDraftCA(post.id, null);
    }
  }, [dispatch, post.id, toggleCommenting]);

  const saveComment = useCallback(() => {
    if (!post.isSavingComment) {
      dispatch(addComment(post.id, textarea.current.value));
      dispatch(updateHighlightedComments());
      typedArrows.current = [];
    }
  }, [dispatch, post.id, post.isSavingComment]);

  const handleKeyDown = useCallback(event => {
    if (event.key === 'Enter' && !event.shiftKey) {
      event.preventDefault();
      setTimeout(saveComment, 0);
    }
  }, [saveComment]);

  const handleKeyUp = useCallback(event => {
    if (event.key === 'Escape') {
      cancelCommenting();
    }
  }, [cancelCommenting]);

  const handleChangeText = useCallback(() => {
    const arrowsFound = textarea.current.value.match(/\^+/g);
    const arrows = (arrowsFound ? arrowsFound.map(a => a.length) : []);

    if (typedArrows.current.length !== arrows.length || !typedArrows.current.every((v, i) => (v === arrows[i]))) { // just comparing two arrays
      typedArrows.current = arrows;
      if (arrows.length > 0) {
        dispatch(updateHighlightedComments({ reason: 'hover-arrows', postId: post.id, baseCommentId: null, arrows }));
      } else {
        dispatch(updateHighlightedComments());
      }
    }

    setDraftCA(post.id, textarea.current.value);
  }, [dispatch, post.id]);

  const needsAddCommentLink = otherCommentsNumber > 2 && !post.omittedComments;
  const draft = getDraftCA(post.id);

  if (!post.isCommenting && !isSinglePost && !draft && !needsAddCommentLink) {
    return false;
  }

  return (
    <div className="comment">
      <a className="comment-icon comment-icon-add" onClick={startCommenting}>
        <Icon name="comment-plus"/>
      </a>

      {post.isCommenting ? (
        <div className="comment-body">
          <Textarea
            inputRef={textareaCallbackRef}
            className="form-control comment-textarea"
            defaultValue={draft ?? ''}
            autoFocus={true}
            onKeyDown={handleKeyDown}
            onKeyUp={handleKeyUp}
            onChange={handleChangeText}
            minRows={2}
            maxRows={10}
            maxLength="1500"/>

          <button className="btn btn-default btn-xs comment-post" onClick={saveComment}>Comment</button>

          <a className="comment-cancel" onClick={cancelCommenting}>Cancel</a>

          {post.isSavingComment ? (
            <Throbber name="comment-edit"/>
          ) : post.commentError ? (
            <div className="comment-error alert alert-danger" role="alert">
              Comment has not been saved. Server response: "{post.commentError}"
            </div>
          ) : false}
        </div>
      ) : (isSinglePost || draft) ? (
        <div className="comment-body">
          <textarea
            className="form-control comment-textarea"
            rows={2}
            placeholder={draft}
            onFocus={startCommenting}/>
        </div>
      ) : <>
        <a className="add-comment-link" onClick={startCommenting}>Add comment</a>

        {post.commentsDisabled && post.canIModerate && (
          <i> - disabled for others</i>
        )}
      </>}
    </div>
  );
};

export default CommentCreateForm;

{'fraction_non_alphanumeric': 0.14540248170537703, 'fraction_numerical': 0.0023332272775479905, 'mean_word_length': 2.6965895727165816, 'pattern_counts': {'":': 0, '<': 51, '<?xml version=': 0, '>': 77, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2759619', 'n_tokens_mistral': 2823, 'n_tokens_neox': 2699, 'n_words': 526}