当前位置: 首页 > 工具软件 > Tocify > 使用案例 >

使用 marked + highlight + tocify.tsx 完成 Markdown(码克党)笔记的渲染

逑彬炳
2023-12-01

前几天看技术胖的视频,做了一个笔记的渲染功能,记录一下做法,以后忘记了可以查看 ,可能不是很理解,先记录 。
一般,我们的文章页面 或是 后台的管理 页面,平常可能要 渲染文章 和 编写文章时的浏览;我们可以使用 以下几个插件来完成这个任务!

marked:      码克党语法的渲染;
highlight:   代码高亮;
tocify.tsx:  提取标题成大纲;


1. 语法的渲染 和 高亮显示

1.1 下载模块

npm install marked highlight.js

1.2 进行配置

我的环境是在 react-nuxt 中的,可能会有一些不同,下面的 文章的详情页面,进行笔记语法的渲染 。

details.js

import React from 'react'
import marked from 'marked'
import hljs from 'highlight.js'
import 'highlight.js/styles/monokai-sublime.css'

function Details () {
  // 配置 marked
  const renderer = new marked.Renderer()
  marked.setOptions({
    renderer: renderer,  // 这个是必须填写的
    gfm: true,  // 启动类似Github样式的Markdown,
    pedantic: false,  // 只解析符合Markdown定义的,不修正Markdown的错误
    sanitize: false,  // 原始输出,忽略HTML标签
    tables: true,  // 支持Github形式的表格,必须打开gfm选项
    breaks: false,  // 支持Github换行符,必须打开gfm选项
    smartLists: true,  // 优化列表输出
    smartypants: false,
    // 高亮显示规则 ,这里使用highlight.js来完成
    highlight: function (code) {
      return hljs.highlightAuto(code).value;
    }
  })

  // 文章详情数据
  const data = 'xxxxxxx,模拟文章内容数据, markdown 语法格式'
  
  // 里面是数据源
  let html = marked(data)
  
  return (
  	<>
      {/* 使用div的属性渲染,才可以有效果 */}
   	  <div dangerouslySetInnerHTML={{__html: html}}></div>
    </>
  )
}

里面的 data 是数据,文章内容可以在 Typora 笔记中写好格式,然后复制黏贴到里面,作为 文章的内容,这样就会有 markdown 的格式;
这样在页面中,就可以渲染 Markdown 语法了;就是样式有点不好看,我们可以使用 css 修饰一下 。



1.3 样式修饰

页面中,使用 F12 检测你的页面结构,将以下的标签进行美化:

/* 代码块 */
code {
  margin: 0 5px;
  padding: 2px 5px;
  background-color: #ffe0e0;
  color: #ff502c;
  border-radius: 5px;
}

/* 代码块 */
pre > code {
    display: block;
    padding: 10px;
    margin: 10px 0;
    background-color: #333;
    border-radius: 5px;
    overflow-y: auto;
    color: #FFF;
    font-family: Menlo, monospace;
}

/* p标签的颜色 */
p { 
	color: #666;
}

ul {
  margin-bottom: 16px;
  font-size: 1rem;
  letter-spacing: 0.1rem;
}

/* li 的项目符号在内侧,显示项目符号 */
ul > li {
   list-style: disc inside;
}

ol {
  font-size: 1rem;
}

ol > li {
   list-style: none;
}

样式你觉得 ok 就可以了 。



2. tocify.tsx 提取大纲

一般的文章详情页面,都有大纲,方便浏览者观看文章,所以呢,我们可以使用他来弄 大纲 。
一般会使用 固钉 把大纲给固定到一个地方,不让他随着页面滚动 (项目中 react 的一个框架, antd 这个 ui 框架)。
这个是使用 技术胖 介绍的插件,大家可以去看看 哔哩哔哩传送门


2.1 条件

要使用这个插件有几个条件;

1,你的项目要使用到 antd 这个ui 框架;才能使用他,因为他依赖到里面的 锚点 Anchor

2,还有下载 lodash 这个插件;

npm install --save @types/lodash

注意,不是 lodash` ,因为要使用到 ts 来写这个文件(是别人的代码);


2.2 创建插件

tocify.tsx 文件(是一个 ts 文件),编写逻辑:

import React from 'react'
import { Anchor } from 'antd'
import { last } from 'lodash'

const { Link } : any = Anchor;


export interface TocItem {
  anchor: string;
  level: number;
  text: string;
  children?: TocItem[];
}

export type TocItems = TocItem[]; // TOC目录树结构

export default class Tocify {
  tocItems: TocItems = [];

  index: number = 0;

  constructor() {
    this.tocItems = [];
    this.index = 0;
  }

  add(text: string, level: number) {
    const anchor = `toc${level}${++this.index}`;
    const item = { anchor, level, text };
    const items = this.tocItems;

    if (items.length === 0) { // 第一个 item 直接 push
      items.push(item);
    } else {
      let lastItem = last(items) as TocItem; // 最后一个 item

      if (item.level > lastItem.level) { // item 是 lastItem 的 children
        for (let i = lastItem.level + 1; i <= 2; i++) {
          const { children } = lastItem;
          if (!children) { // 如果 children 不存在
            lastItem.children = [item];
            break;
          }

          lastItem = last(children) as TocItem; // 重置 lastItem 为 children 的最后一个 item

          if (item.level <= lastItem.level) { // item level 小于或等于 lastItem level 都视为与 children 同级
            children.push(item);
            break;
          }
        }
      } else { // 置于最顶级
        items.push(item);
      }
    }

    return anchor;
  }

  reset = () => {
    this.tocItems = [];
    this.index = 0;
  };

  renderToc(items: TocItem[]) { // 递归 render
    return items.map(item => (
      <Link key={item.anchor} href={`#${item.anchor}`} title={item.text}>
        {item.children && this.renderToc(item.children)}
      </Link>
    ));
  }

  render() {
    return (
      <Anchor affix showInkInFixed targetOffset={100}>
         {this.renderToc(this.tocItems)}
      </Anchor>
    );
  }
}

上面的代码是我复制黏贴的,因为是 ts 文件,会有几个方面要注意的:

1:引入的组件要先说明,再使用:

import { Anchor } from 'antd'
import { last } from 'lodash'

const { Link } : any = Anchor;

我也不会,所以给 Link 添加了 any 这个类型;在 vscode 中会警告说 组件没有注册,差不多这个意思,不管他好像没有错误(嘻嘻嘻,别骂我);
求求大佬解答?



2.3 创建插件

在文章详情页面导入文件,然后进行配置

web > pages > details.js

// 后来的 马克党
import marked from 'marked'
import hljs from 'highlight.js'
import 'highlight.js/styles/monokai-sublime.css'

// 生成目录,注意不要写错 Tocify / tocify
import Tocify from './api/tocify.tsx'

function Details () {
  // 之前配置 marked  笔记渲染的代码
  const renderer = new marked.Renderer()
  marked.setOptions({
    renderer: renderer,
    gfm: true,
    pedantic: false,
    sanitize: false,
    tables: true,
    breaks: false,
    smartLists: true,
    smartypants: false,
    highlight: function (code) {
      return hljs.highlightAuto(code).value;
    }
  })
    
  // 配置 Tocify 大纲
  const tocify = new Tocify()
  // 前面有 marked + highlight 配置,是一起作用的
  renderer.heading = function(text, level, raw) {
    const anchor = tocify.add(text, level)
    return `<a id="${anchor}" href="#${anchor}" class="anchor-fix"><h${level}> ${text} </h${level}></a> \n`
  }
    
   return (
   	  <>
       	{/* 固钉-大纲 */}
        <Affix>
          <div>
            <h3>大纲:</h3>
            {/* 就是这个 */}
            {tocify && tocify.render()}
          </div>
        </Affix>
      </>
   )
}

export default Details

一般放在固钉里,固定位置,
如需,控制 内容 的位置,可以在 tsx 文件中调整 Anchor,就是点击大纲,文章进行定位,如果要调整文章定位的位置,可以使用 targetOffset (距离顶部位置)进行调整;

    return (
      <Anchor affix showInkInFixed targetOffset={100}>
         {this.renderToc(this.tocItems)}
      </Anchor>
    );
 类似资料: