美文网首页
react contentEditable 可输入div 高亮关

react contentEditable 可输入div 高亮关

作者: Spidd | 来源:发表于2022-02-21 17:49 被阅读0次
/*
 * 开发调试
 */
import React, { useEffect, useRef } from 'react';
import PropTypes from 'prop-types';
import styles from './style.module.less';

const keyword = ['const'];
const replaceBlank = (html) => html.replace(/ /ig, ' ');

// 获取光标位置
function getCaretCharacterOffsetWithin(element) {
  let caretOffset = 0;
  const doc = element.ownerDocument || element.document;
  const win = doc.defaultView || doc.parentWindow;
  let sel;
  if (typeof win.getSelection !== 'undefined') {
    sel = win.getSelection();
    if (sel.rangeCount > 0) {
      const range = win.getSelection().getRangeAt(0);
      const preCaretRange = range.cloneRange();
      preCaretRange.selectNodeContents(element);
      preCaretRange.setEnd(range.endContainer, range.endOffset);
      caretOffset = preCaretRange.toString().length;
    }
  } else if ((sel = doc.selection) && sel.type != 'Control') {
    const textRange = sel.createRange();
    const preCaretTextRange = doc.body.createTextRange();
    preCaretTextRange.moveToElementText(element);
    preCaretTextRange.setEndPoint('EndToEnd', textRange);
    caretOffset = preCaretTextRange.text.length;
  }
  return caretOffset;
}

// 设置光标位置
function setCaretPosition(element, offset) {
  const range = document.createRange();
  const sel = window.getSelection();

  // select appropriate node
  let currentNode = null;
  let previousNode = null;

  for (let i = 0; i < element.childNodes.length; i++) {
    // save previous node
    previousNode = currentNode;

    // get current node
    currentNode = element.childNodes[i];
    // if we get span or something else then we should get child node
    while (currentNode.childNodes.length > 0) {
      [currentNode] = currentNode.childNodes;
    }

    // calc offset in current node
    if (previousNode != null) {
      offset -= previousNode.length;
    }
    // check whether current node has enough length
    if (offset <= currentNode.length) {
      break;
    }
  }
  // move caret to specified offset
  if (currentNode != null) {
    range.setStart(currentNode, offset);
    range.collapse(true);
    sel.removeAllRanges();
    sel.addRange(range);
  }
}

// 排序方法
function compareWordLength(a, b) {
  if (a.length > b.length) {
    return -1;
  } if (a.length < b.length) {
    return 1;
  }
  return 0;
}

// 高亮关键字
function addKeyWordHighline(oText, keyWords) {
  let returnVal = oText;
  let i = 0;
  let wordReg;
  keyWords.sort(compareWordLength);

  for (i = 0; i < keyWords.length; i++) {
    if (keyWords[i] !== '') {
      wordReg = new RegExp(`(?!<span+>.[^<]*)${keyWords[i]}(?!.[^<]*<\/span>)`, 'g');
      returnVal = returnVal.replace(wordReg, `<span>${keyWords[i]}</span>`);
    }
  }
  return returnVal;
}

const CodeInput = ({
  value,
  disable,
  children,
  onChange,
}) => {
  const ref = useRef();
  // 是否锁定输入
  const isLock = useRef(false);

  const getRef = () => ref && ref.current;

  const onCompositionstart = (e) => {
    isLock.current = true;
  };
  const onCompositionend = (e) => {
    isLock.current = false;
  };

  // 解决中文输入的时候,直接输出英文字母的问题(中文输入期间,不允许输入字符)
  useEffect(() => {
    // 监听中文输入
    const el = getRef();
    el.addEventListener('compositionstart', onCompositionstart, false);
    el.addEventListener('compositionend', onCompositionend, false);
    return () => {
      el.removeEventListener('compositionstart', onCompositionstart, false);
      el.removeEventListener('compositionend', onCompositionend, false);
    };
  }, []);

  const onInput = () => {
    const el = getRef();
    // dom是否为空 || 是否为锁定模式
    if (!el || isLock.current) return;
    // 获取内容
    let text = el.innerHTML;
    // 是否修改了
    if (value !== text) {
      // 获取光标
      const position = getCaretCharacterOffsetWithin(el);
      // 替换空格
      text = replaceBlank(text);
      // 替换关键字
      text = addKeyWordHighline(text, keyword);
      el.innerHTML = text;
      // 更新父组件
      onChange(text);
      // 恢复位置
      setCaretPosition(el, position);
    }
  };

  return (
    <pre
      ref={ref}
      className={styles['formula-input']}
      contentEditable={!disable}
      dangerouslySetInnerHTML={{ __html: value }}
      onInput={onInput}
    >

      {children}
    </pre>
  );
};

CodeInput.propTypes = {
  value: PropTypes.array,
  disable: PropTypes.number,
  onChange: PropTypes.func,
  children: PropTypes.node,
};

CodeInput.defaultProps = {
  value: '默认',   // value
  disable: false,   // 是否可用
  children: undefined, // 子元素
  onChange: () => {},
};

export default CodeInput;

相关文章

网友评论

      本文标题:react contentEditable 可输入div 高亮关

      本文链接:https://www.haomeiwen.com/subject/mjuilrtx.html