Marked.js 的使用及相关问题解决

一、为什么使用Marked.js

marked.js 用于将 markdown 格式字符串转换为对应的带有 html 标签包裹的字符串。通常适用于 AI 产品的聊天窗口,将模型返回的 markdown 格式的字符串转换成对应格式的 html 标签,在聊天窗口中显示。

二、marked.js 的使用方法

1,安装 marked.js

npm i marked / yarn add marked / pnpm install marked

2,使用 marked.js

javascript 复制代码
<template>
    <p v-html="marked(message)"><p/>
<template/>
<script setup>
import { ref} from 'vue'
import {marked} from 'marked'

const message = ref()


<script/>

三、配置换行

因为 marked.js 直接使用是默认不换行的,所以如果想要使 marked.js 的换行效果同后端返回的字符串样式对齐的话,需要对其进行配置 breaks : true ;

javascript 复制代码
marked.setOptions({
  gfm: true,        // 使用 GitHub Flavored Markdown (GFM)
  breaks: true,     // 转换换行符为 <br> 标签
  pedantic: false,  // 不遵守原始的 Markdown 标准
  sanitize: false,  // 不对输出进行清理/转义(默认情况下会转义 HTML)
  smartLists: true, // 使用智能列表识别(例如正确的缩进)
  smartypants: false // 不使用智能引号处理
});

四、重写 p 标签渲染事件, 解决空行丢失问题

如果后端传回的字符串后面带连续的两个\n,在转为 markdown 的时候是需要出现一个空行的,但是 marked.js 会把 \n\n 后面的一段文本转换成 p 标签,所以,空行会消失。所以需要重写 p 标签的渲染事件,paragraph 在 p 标签前面增加一个换行 br 标签

javascript 复制代码
import { marked, Renderer} from 'marked';

// 重写marked 渲染逻辑
const renderer = new Renderer();

marked.setOptions({
  gfm: true,
  smartLists: true,
  renderer: renderer,
  breaks: true,
});

renderer.paragraph = (data) => {
  return "</br>" + "<p>" + marked.parseInline(data.raw) + "</p>";
};

五、重写 li 标签渲染事件, 解决转换有序列表字符串为 li 时,序号丢失问题。

当 marked.js 转换字符串中的有序列表时,会造成序号丢失问题。

例如:1, aaaaaaaaaa。/n 2,bbbbbbbbbb。

marked.js 会将其转换成 <li>aaaaaaaaaa。<li> <li>bbbbbbbbbb。<li/> 。 造成 首位数字的丢失

这里是指 从 1 开始的有序列表转换时会造成 数字的丢失,但是不是从 1 开始的并不会丢失序号数字。

可以通过改写 li 标签的渲染事件 listitem。解决该问题。

javascript 复制代码
// 重写marked 的有序列表,子元素 li 渲染逻辑
renderer.listitem = (data) => {
  // 普通列表项添加自定义 class
  let liTextList;
  liTextList = marked.parseInline(data.raw).split("<br>") as string[];
  // 根据每个字符串中开始的空格缩进来分组用li 包裹
  if (liTextList.length > 1) {
    return liTextList.reduce((prev, curr) => {
      const spaceLength = curr.match(/^\s*/)?.[0].length;
      // 首行有空格,有缩进,并不代表是有 有序列表或者无序列表
      if (spaceLength) {
        // 计算当前字符串的缩进层级
        const level = Math.floor(spaceLength / 2);
        let resultHtmlString = prev + "<li";
        // 此时才是有有序列表或者无序列表的情况
        if (/^ +\* +/.test(curr)) {
          resultHtmlString += ` class='li-level-${level}'>`;
          curr = curr.replace(/^ +\* +/, "");
        } else {
          resultHtmlString += `>`;
        }

        return resultHtmlString + curr + "</li>";
      } else if (/^\* +/.test(curr)) {
        curr = curr.replace(/^\* */, "");
        // 以* 开头,没有缩进,直接是黑色圆点无序列表
        return prev + `<li class="li-level-0">${curr}</li>`;
      } else {
        return prev + `<li>${curr}</li>`;
      }
    }, "");
  } else if (liTextList[0] && /^\* +/.test(liTextList[0])) {
    data.raw = data.raw.replace(/^\* +/, "");
    // 以* 开头,没有缩进,直接是黑色圆点无序列表
    return "<li class='li-level-0'>" + marked.parseInline(data.raw) + "</li>";
  } else {
    // 只有一行的情况下,直接渲染
    return "<li>" + marked.parseInline(data.raw) + "</li>";
  }
};

再根据 level 层级修改对应的 li 标签的样式属性,增加不同的缩进,以及 list-style-type

对于无序列表ul 来说,内层嵌套 li 层级的 list-style-type 的顺序是 disc:黑色小圆点。circle:白色小圆点。square:黑色小方块。依次循环

css 复制代码
  .li-level-0 {
    display: list-item;
    margin-left: 30px;
    list-style-position: outside;
    list-style-type: disc;
  }

  .li-level-2 {
    display: list-item;
    margin-left: 60px;
    list-style-position: outside;
    list-style-type: circle;
  }
  .li-level-4 {
    display: list-item;
    margin-left: 90px;
    list-style-position: outside;
    list-style-type: square;
  }
相关推荐
爱勇宝4 小时前
大多数人不是在使用 AI 赚钱,而是在帮 AI 公司赚钱
前端·后端·程序员
冬奇Lab5 小时前
每日一个开源项目(第143篇):page-agent - 纯 JS 的网页 GUI Agent,无需截图、无需插件、无需后端
前端·人工智能·agent
IT_陈寒10 小时前
React的这个渲染问题连官方文档都没说清楚
前端·人工智能·后端
追逐时光者11 小时前
别再满网找零散工具了,腾讯 QQ 浏览器这个“帮小忙”工具箱真能省时间
前端·后端
Asmewill13 小时前
grep&curl命令学习笔记
前端
stringwu13 小时前
Flutter 开发必备:MVI 架构的高效实现指南
前端·flutter
用户21366100357214 小时前
Vue2组件化开发与父子通信
前端·vue.js
Momo__15 小时前
TypeScript satisfies 操作符——比 as 更安全的类型守门员
前端·typescript
用户21366100357215 小时前
Vue2事件系统与指令进阶
前端·vue.js
labixiong15 小时前
实现一个能跑的迷你版Promise(一)
前端·javascript·面试