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;
  }
相关推荐
松涛和鸣几秒前
48、MQTT 3.1.1
linux·前端·网络·数据库·tcp/ip·html
helloworld也报错?1 分钟前
保存网页为PDF
前端·javascript·pdf
码丁_1172 分钟前
某it培训机构前端三阶段react及新增面试题
前端·react.js·前端框架
石小石Orz2 分钟前
自定义AI智能体扫描内存泄漏代码
前端·ai编程
_木棠3 分钟前
uniapp:H5端reLaunch跳转后,返回还有页面存在问题
前端·uni-app
梦凡尘18 分钟前
前端web端解析 Word、Pdf 文档文本内容
pdf·js
纳兰瑞雪18 分钟前
nodeJs electron程式开发demo
开发语言·前端·javascript
why技术18 分钟前
可怕,看到一个冷血的算法。人心逐利,算法只会更聪明地逐利。
前端·后端·算法
祎直向前44 分钟前
linuxshell循环,条件分支语句
前端·chrome
LongtengGensSupreme44 分钟前
开放所有跨域 ----前端和后端
前端·后端·ajax·vue·api·jquery