All files Editor.tsx

100% Statements 22/22
100% Branches 30/30
100% Functions 7/7
100% Lines 19/19

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136                                                                                                            69x   69x 69x 69x 69x   69x             69x   53x               69x   51x                       69x 19x 19x     69x 21x 20x 19x       69x                                       69x                      
import React, { useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
import { PluggableList } from 'unified';
import { processHtml, htmlEncode } from './utils';
import shortcuts from './shortcuts';
import * as styles from './styles';
import './style/index.less';
 
export * from './SelectionText';
 
export interface TextareaCodeEditorProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
  prefixCls?: string;
  /**
   * Support dark-mode/night-mode
   */
  ['data-color-mode']?: 'dark' | 'light';
  /**
   * Set what programming language the code belongs to.
   */
  language?: string;
  /**
   * Optional padding for code. Default: `10`.
   */
  padding?: number;
  /**
   * rehypePlugins (Array.<Plugin>, default: `[[rehypePrism, { ignoreMissing: true }]]`)
   * List of [rehype plugins](https://github.com/rehypejs/rehype/blob/main/doc/plugins.md#list-of-plugins) to use. See the next section for examples on how to pass options
   */
  rehypePlugins?: PluggableList;
  /**
   * The minimum height of the editor. Default: `16`.
   */
  minHeight?: number;
  onKeyDown?: (event: React.KeyboardEvent<HTMLTextAreaElement>) => void | boolean;
  /**
   * The number of spaces for indentation when pressing tab key. Default: `2`.
   */
  indentWidth?: number;
}
 
export default React.forwardRef<HTMLTextAreaElement, TextareaCodeEditorProps>((props, ref) => {
  const {
    prefixCls = 'w-tc-editor',
    value: _,
    padding = 10,
    minHeight = 16,
    placeholder,
    language,
    'data-color-mode': dataColorMode,
    className,
    style,
    rehypePlugins,
    onChange,
    indentWidth = 2,
    ...other
  } = props;
 
  const [value, setValue] = useState(props.value || '');
  useEffect(() => setValue(props.value || ''), [props.value]);
  const textRef = useRef<HTMLTextAreaElement>(null);
  useImperativeHandle<HTMLTextAreaElement, HTMLTextAreaElement>(ref, () => textRef.current!, [textRef]);
 
  const contentStyle = {
    paddingTop: padding,
    paddingRight: padding,
    paddingBottom: padding,
    paddingLeft: padding,
  };
 
  const htmlStr = useMemo(
    () =>
      processHtml(
        `<pre aria-hidden=true><code ${language && value ? `class="language-${language}"` : ''} >${htmlEncode(
          String(value || ''),
        )}</code><br /></pre>`,
        rehypePlugins,
      ),
    [value, language, rehypePlugins],
  );
  const preView = useMemo(
    () => (
      <div
        style={{ ...styles.editor, ...contentStyle, minHeight }}
        className={`${prefixCls}-preview ${language ? `language-${language}` : ''}`}
        dangerouslySetInnerHTML={{
          __html: htmlStr,
        }}
      />
    ),
    // eslint-disable-next-line react-hooks/exhaustive-deps
    [prefixCls, language, htmlStr, padding, minHeight],
  );
 
  const change = (evn: React.ChangeEvent<HTMLTextAreaElement>) => {
    setValue(evn.target.value);
    onChange && onChange(evn);
  };
 
  const keyDown = (evn: React.KeyboardEvent<HTMLTextAreaElement>) => {
    if (other.readOnly) return;
    if (!other.onKeyDown || other.onKeyDown(evn) !== false) {
      shortcuts(evn, indentWidth);
    }
  };
 
  const textareaProps: React.TextareaHTMLAttributes<HTMLTextAreaElement> = {
    autoComplete: 'off',
    autoCorrect: 'off',
    spellCheck: 'false',
    autoCapitalize: 'off',
    ...other,
    placeholder,
    onKeyDown: keyDown,
    style: {
      ...styles.editor,
      ...styles.textarea,
      ...contentStyle,
      minHeight,
      ...(placeholder && !value ? { WebkitTextFillColor: 'inherit' } : {}),
    },
    onChange: change,
    className: `${prefixCls}-text`,
    value: value,
  };
 
  return (
    <div
      style={{ ...styles.container, ...style }}
      className={`${prefixCls} ${className || ''}`}
      data-color-mode={dataColorMode}
    >
      <textarea {...textareaProps} ref={textRef} />
      {preView}
    </div>
  );
});