ifmeorg/ifme

View on GitHub
client/app/components/Input/InputTextarea.jsx

Summary

Maintainability
A
0 mins
Test Coverage
// @flow
import React, { useState, useEffect, useRef } from 'react';
import type { Node } from 'react';
import { sanitize } from 'dompurify';
import ReactDOMServer from 'react-dom/server';
import { init, exec } from 'pell';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faImage, faLink } from '@fortawesome/free-solid-svg-icons';
import css from './InputTextarea.scss';
import inputCss from './Input.scss';

const handleResult = (type: string) => {
  switch (type) {
    case 'link': {
      const url = window.prompt('URL');
      if (url) exec('createLink', url);
      break;
    }
    case 'image': {
      const src = window.prompt('Please provide a link to your image.');
      if (src) exec('insertImage', src);
      break;
    }
    case 'olist':
      exec('insertOrderedList');
      break;
    case 'ulist':
      exec('insertUnorderedList');
      break;
    default:
      exec(type);
      break;
  }
  return false;
};

const action = (type: string) => ({
  name: type,
  result: () => handleResult(type),
});

const actions = [
  action('bold'),
  action('italic'),
  action('underline'),
  action('strikethrough'),
  action('olist'),
  action('ulist'),
  {
    ...action('link'),
    icon: ReactDOMServer.renderToString(<FontAwesomeIcon icon={faLink} />),
  },
  {
    ...action('image'),
    icon: ReactDOMServer.renderToString(<FontAwesomeIcon icon={faImage} />),
  },
];

const classes = {
  button: css.button,
  selected: css.buttonSelected,
  content: `editorContent ${css.content}`,
};

export type Props = {
  id: string,
  name?: string,
  value?: any,
  required?: boolean,
  hasError?: Function,
  myRef?: any,
  dark?: boolean,
};

export function InputTextarea(props: Props): Node {
  const {
    id, name, value: propValue, required, hasError, myRef, dark,
  } = props;
  const [value, setValue] = useState<string>(sanitize(propValue) || '');
  const editorRef = useRef(null);
  const editor = useRef(null);

  const onChange = (updatedValue: string) => {
    setValue(sanitize(updatedValue));
  };

  const onBlur = () => {
    if (required && hasError) {
      hasError(!value || value === '<p><br /></p>');
    }
  };

  const onFocus = () => {
    if (required && hasError) {
      hasError(false);
    }
    if (editorRef.current) {
      editorRef.current.getElementsByClassName('editorContent')[0].focus();
    }
  };

  const onPaste = (e) => {
    e.preventDefault();

    const text = (e.originalEvent || e).clipboardData.getData('text/plain') ?? '';

    document.execCommand('insertHTML', false, sanitize(text));
  };

  useEffect(() => {
    if (editorRef.current) {
      editor.current = init({
        element: editorRef.current.getElementsByClassName('editor')[0],
        onChange,
        classes,
        actions,
      });
      editor.current.content.innerHTML = value;
    }
  }, []);

  return (
    <div
      id={id}
      className={`${inputCss.default} ${dark ? css.dark : ''}`}
      onBlur={onBlur}
      onFocus={onFocus}
      onPaste={onPaste}
      tabIndex={0}
      role="textbox"
      ref={editorRef}
    >
      <div className={`editor ${css.editor}`} />
      <input
        type="hidden"
        value={value}
        name={name}
        required={required}
        ref={myRef}
      />
    </div>
  );
}