带格式(换行、缩进)复制文本解决方案

0 阅读2分钟

起因

我在对接流式读取后有一个复制按钮,点击复制的是纯文本,整个都黏在一起了,非常土,因此秉持着不懂就问的精神,我问了豆包大神,一开始它给我的是marked方案,效果不彳亍,一些行首尾空格都给我干掉了。后面让它统一使用unified链式处理就彳亍了

de465337cfe17a3b897e7dc62a2b2c21.png

代码

/**
 * 用 unified 解析 Markdown,返回 HTML 和保留格式的纯文本
 * @param markdown 原始 Markdown 文本
 * @returns { html: string, text: string } 解析后的 HTML 和纯文本
 */
async function parseMarkdownWithUnified(markdown: string) {
  if (!markdown) return { html: '', text: '' }

  try {
    // 1. 移除 rehype-format(避免自动添加多余缩进/换行)
    const processed = await unified()
      .use(remarkParse)
      .use(remarkGfm)
      .use(remarkRehype)
      // 去掉 rehype-format,避免 HTML 自动格式化导致多余空白
      .use(rehypeStringify)
      .process(markdown)

    const html = String(processed)
    const tempElement = document.createElement('div')
    tempElement.innerHTML = html

    // 2. 关键:清理纯文本的多余空白(保留必要换行,合并连续空行)
    let text = tempElement.textContent || ''
    // 正则规则:
    // - 合并连续的多个空行(保留1个换行)
    // - 移除行首/行尾的多余空格(保留缩进的必要空格)
    text = text.replace(/\n{3,}/g, '\n\n') // 连续3个以上换行 → 保留2个(段落间距)

    return { html, text }
  } catch (err) {
    console.error('unified 解析 Markdown 失败:', err)
    // 降级时也清理空白
    let fallbackText = markdown.replace(/\n{3,}/g, '\n\n').trim()
    return { html: markdown, text: fallbackText }
  }
}

async function handleCopy() {
  try {
    const rawMarkdown = props.msg.content || ''
    if (!rawMarkdown) {
      showSuccessToast('暂无内容可复制')
      return
    }

    const { html, text } = await parseMarkdownWithUnified(rawMarkdown)

    if (navigator.clipboard && window.ClipboardItem) {
      await navigator.clipboard.write([
        new ClipboardItem({
          'text/plain': new Blob([text], { type: 'text/plain' }),
          'text/html': new Blob([html], { type: 'text/html' })
        })
      ])
      showSuccessToast('已复制到剪贴板')
    } else {
      const tempTextarea = document.createElement('textarea')
      tempTextarea.style.position = 'absolute'
      tempTextarea.style.left = '-9999px'
      tempTextarea.style.top = '-9999px'
      tempTextarea.value = text
      document.body.appendChild(tempTextarea)
      tempTextarea.select()
      tempTextarea.setSelectionRange(0, tempTextarea.value.length)

      const success = document.execCommand('copy')
      if (success) {
        showSuccessToast('已复制到剪贴板')
      } else {
        throw new Error('execCommand 复制失败')
      }
      document.body.removeChild(tempTextarea)
    }
  } catch (err) {
    console.error('复制失败:', err)
    showSuccessToast('复制失败,请手动复制')
  }
}

效果

image.png

b259c9bbe2178babcbe6155ced411f6c.png

结论

我的评价给到“夯”,我是写不出这样的代码,都不敢想啊