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

如何使Markdown渲染的复选框可勾选并同步修改原Markdown内容?

解决React+Unified+CodeMirror下Markdown复选框交互与同步问题

一、修复复选框无法勾选的问题

默认通过remark-gfm转换Markdown任务列表时,生成的<input>标签会自带disabled属性,且静态渲染的HTML即使手动移除disabled也没有交互逻辑。解决步骤:

  1. 自定义Rehype插件移除禁用属性
    遍历转换后的AST节点,移除复选框的disabled属性,并添加自定义标识绑定对应Markdown行:
    import { rehype } from 'rehype';
    import rehypeRaw from 'rehype-raw';
    import visit from 'unist-util-visit';
    
    const removeCheckboxDisabled = () => {
      return (tree) => {
        visit(tree, 'element', (node) => {
          if (node.tagName === 'input' && node.properties.type === 'checkbox') {
            delete node.properties.disabled;
            // 绑定对应Markdown行索引的自定义属性
            node.properties['data-line-index'] = node.parent?.properties?.['data-line'] || '';
          }
        });
      };
    };
    
    // 整合进Unified转换流程
    const markdownToHtml = async (md) => {
      const result = await unified()
        .use(remarkParse)
        .use(remarkGfm)
        .use(remarkRehype, { allowDangerousHtml: true })
        .use(rehypeRaw)
        .use(removeCheckboxDisabled)
        .use(rehypeStringify)
        .process(md);
      return String(result);
    };
    
  2. 给预览区绑定事件委托
    避免用dangerouslySetInnerHTML后完全静态化,通过事件委托监听复选框点击:
    const Preview = ({ html, onCheckboxChange }) => {
      const handleClick = (e) => {
        if (e.target.type === 'checkbox') {
          e.preventDefault();
          const lineIndex = parseInt(e.target.dataset.lineIndex);
          onCheckboxChange(lineIndex, e.target.checked);
        }
      };
    
      return (
        <div 
          className="preview" 
          dangerouslySetInnerHTML={{ __html: html }}
          onClick={handleClick}
        />
      );
    };
    

二、同步预览状态到原Markdown文本

核心是通过行索引找到对应Markdown行,修改[ ]/[x]标记后更新CodeMirror内容:

  1. 预处理Markdown标记任务列表行号
    解析Markdown每一行,标记任务列表的行索引,在转换时给<li>标签绑定行号属性:
    // 获取所有任务列表的行索引
    const getTaskLineIndices = (md) => {
      const lines = md.split('\n');
      return lines.map((line, index) => {
        return /^\s*[-*+]\s*\[( |x)\]\s+/.test(line) ? index : null;
      }).filter(i => i !== null);
    };
    
    // 给任务列表项添加行号属性
    const addTaskLineAttrs = (lineIndices) => {
      return () => {
        return (tree) => {
          let taskIndex = 0;
          visit(tree, 'listItem', (node) => {
            if (node.checked !== undefined) {
              node.data = node.data || {};
              node.data.hProperties = node.data.hProperties || {};
              node.data.hProperties['data-line'] = lineIndices[taskIndex++];
            }
          });
        };
      };
    };
    
    // 更新转换流程,加入行号标记
    const markdownToHtml = async (md) => {
      const taskLines = getTaskLineIndices(md);
      const result = await unified()
        .use(remarkParse)
        .use(remarkGfm)
        .use(addTaskLineAttrs, taskLines)
        .use(remarkRehype, { allowDangerousHtml: true })
        .use(rehypeRaw)
        .use(removeCheckboxDisabled)
        .use(rehypeStringify)
        .process(md);
      return String(result);
    };
    
  2. 实现状态同步逻辑
    在React组件中处理复选框变化,更新原始Markdown文本:
    const Editor = () => {
      const [markdown, setMarkdown] = useState('- [ ] 待办事项1\n- [x] 已完成事项');
      const [html, setHtml] = useState('');
    
      // 实时转换Markdown到HTML
      useEffect(() => {
        const convert = async () => {
          const result = await markdownToHtml(markdown);
          setHtml(result);
        };
        convert();
      }, [markdown]);
    
      // 同步复选框状态到Markdown
      const handleCheckboxChange = (lineIndex, isChecked) => {
        const lines = markdown.split('\n');
        const targetLine = lines[lineIndex];
        // 替换复选框状态标记
        const updatedLine = isChecked 
          ? targetLine.replace(/^\s*[-*+]\s*\[( |x)\]/, '$1[x]')
          : targetLine.replace(/^\s*[-*+]\s*\[( |x)\]/, '$1[ ]');
        lines[lineIndex] = updatedLine;
        setMarkdown(lines.join('\n'));
      };
    
      return (
        <div className="editor-container">
          <CodeMirror value={markdown} onChange={(value) => setMarkdown(value)} />
          <Preview html={html} onCheckboxChange={handleCheckboxChange} />
        </div>
      );
    };
    

三、注意事项

  • 确保安装依赖:unist-util-visit(遍历AST节点)、remark-gfm(支持任务列表语法)
  • 嵌套任务列表需调整行索引解析逻辑,确保每个复选框对应正确行
  • CodeMirror若使用@codemirror/next系列,需适配其onChange回调格式

内容的提问来源于stack exchange,提问作者dsadsadsadas dsadasdasda

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 23:04:50