You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在React的Draft.js中实现自定义快捷文本格式功能?

解决Draft.js的Markdown风格快捷输入需求

下面是针对你提出的四个快捷输入需求的完整实现代码,替换现有代码即可直接使用:

import React, { useState, useCallback } from 'react';
import { Editor, EditorState, RichUtils, getDefaultKeyBinding, Modifier, SelectionState } from 'draft-js';
import 'draft-js/dist/Draft.css';

export default function App() {
  const [editorState, setEditorState] = useState(EditorState.createEmpty());

  // 获取光标所在行的前缀文本
  const getCurrentLinePrefix = (editorState) => {
    const selection = editorState.getSelection();
    const contentState = editorState.getCurrentContent();
    const block = contentState.getBlockForKey(selection.getStartKey());
    return block.getText().slice(0, selection.getStartOffset()).trim();
  };

  const keyBindingFn = useCallback((event) => {
    if (event.keyCode === 32) { // 监听空格按键触发快捷规则
      return "apply-markdown-shortcut";
    }
    return getDefaultKeyBinding(event);
  }, []);

  const handleKeyCommand = useCallback(
    (command) => {
      if (command === "apply-markdown-shortcut") {
        const prefix = getCurrentLinePrefix(editorState);
        const contentState = editorState.getCurrentContent();
        const selection = editorState.getSelection();
        const blockKey = selection.getStartKey();
        let newEditorState = editorState;
        let removeLength = 0;

        // 需求1:# + 空格 → 转为一级标题并删除#
        if (prefix === '#') {
          removeLength = 2; // # + 空格的总长度
          // 删除前缀字符
          const updatedContent = Modifier.replaceText(
            contentState,
            SelectionState.createEmpty(blockKey).merge({
              anchorOffset: 0,
              focusOffset: removeLength
            }),
            ''
          );
          newEditorState = EditorState.push(editorState, updatedContent, 'remove-range');
          // 应用标题格式
          newEditorState = RichUtils.toggleBlockType(newEditorState, 'header-one');
          setEditorState(newEditorState);
          return "handled";
        }

        // 需求2:* + 空格 → 转为粗体并删除*
        if (prefix === '*') {
          removeLength = 2;
          const updatedContent = Modifier.replaceText(
            contentState,
            SelectionState.createEmpty(blockKey).merge({
              anchorOffset: 0,
              focusOffset: removeLength
            }),
            ''
          );
          newEditorState = EditorState.push(editorState, updatedContent, 'remove-range');
          // 应用粗体样式
          newEditorState = RichUtils.toggleInlineStyle(newEditorState, 'BOLD');
          setEditorState(newEditorState);
          return "handled";
        }

        // 需求3:** + 空格 → 红色下划线并删除**
        if (prefix === '**') {
          removeLength = 3;
          const updatedContent = Modifier.replaceText(
            contentState,
            SelectionState.createEmpty(blockKey).merge({
              anchorOffset: 0,
              focusOffset: removeLength
            }),
            ''
          );
          newEditorState = EditorState.push(editorState, updatedContent, 'remove-range');
          // 应用自定义红色下划线样式
          newEditorState = RichUtils.toggleInlineStyle(newEditorState, 'RED_UNDERLINE');
          setEditorState(newEditorState);
          return "handled";
        }

        // 需求4:*** + 空格 → 下划线并删除***
        if (prefix === '***') {
          removeLength = 4;
          const updatedContent = Modifier.replaceText(
            contentState,
            SelectionState.createEmpty(blockKey).merge({
              anchorOffset: 0,
              focusOffset: removeLength
            }),
            ''
          );
          newEditorState = EditorState.push(editorState, updatedContent, 'remove-range');
          // 应用下划线样式
          newEditorState = RichUtils.toggleInlineStyle(newEditorState, 'UNDERLINE');
          setEditorState(newEditorState);
          return "handled";
        }

        return "not-handled";
      }
      return "not-handled";
    },
    [editorState]
  );

  // 自定义红色下划线样式映射
  const customStyleMap = {
    RED_UNDERLINE: {
      textDecoration: 'underline',
      textDecorationColor: 'red',
    },
  };

  return (
    <div style={{ border: "2px solid black", padding: '10px' }}>
      <Editor
        editorState={editorState}
        onChange={setEditorState}
        handleKeyCommand={handleKeyCommand}
        keyBindingFn={keyBindingFn}
        customStyleMap={customStyleMap}
      />
    </div>
  );
}

核心实现细节:

  • 前缀检测:通过getCurrentLinePrefix获取光标所在行的纯文本前缀,判断是否匹配快捷规则
  • 精准删除:使用Modifier.replaceText删除行首的符号及后续空格,确保无无效字符残留
  • 格式应用:
    • 标题用RichUtils.toggleBlockType设置块级格式
    • 粗体、下划线用RichUtils.toggleInlineStyle设置行内样式,红色下划线通过customStyleMap自定义实现
  • 光标自动校准:删除前缀后自动调整光标位置,保证输入流程顺畅

内容的提问来源于stack exchange,提问作者nidhi sharma

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 02:13:26