我是如何在bytemd中实现自定义目录的

介绍

接着上文说完,实现了在markdown编辑器中插入视频的能力,接下来还需要继续优化 markdown文档的阅读体验,比如 再加个目录

熟悉markdown语法的朋友可能会说,直接在编辑时添加 @toc 标签,可以在文章顶部自动生成目录,但是这并不是我们想要的效果。我们想要什么效果呢,就和掘金这种效果一样(🤓️)。找了一圈没有看到 bytemd有自带的ToC组件,于是决定自行实现目录效果。

目录主要是展示的时候用,所以只需要处理查看页的相关逻辑。写之前也有参考bytemd自带预览视图的目录效果,不过不太好直接复用,因为实际上我们的目录还需要 - 1. 响应点击定位到具体的片段、2. 自定义样式效果 (其实主要原因是 项目开了es严格检查,直接copy过来的目录代码要改的东西太多。。。)

UI层

我们先实现目录的UI组件

export interface Heading {id: string,text: string,level: number
}interface TocProps {hast: Heading[];currentBlockIndex: number;onTocClick: (clickIndex: number) => void;
}
const Toc: React.FC<TocProps> = ({ hast, currentBlockIndex, onTocClick}) => {const [items, setItems] = useState<Heading[]>([]);const [minLevel, setMinLevel] = useState(6);const [currentHeadingIndex, setCurrentHeadingIndex] = useState(0);useEffect(() => {let newMinLevel = 6;setCurrentHeadingIndex(currentBlockIndex);setItems(hast);hast.forEach((item, index) => {newMinLevel = Math.min(newMinLevel, item.level);})setMinLevel(newMinLevel);}, [hast, currentBlockIndex]);const handleClick = (index: number) => {onTocClick(index);};return (<div className={`bytemd-toc`}><h2 style={{marginBottom: '0.5em', fontSize: '16px'}}>目录</h2><div className={styles.tocDivider}/><ul>{items.map((item, index) => (<likey={index}className={`bytemd-toc-${item.level} ${currentHeadingIndex === index ? 'bytemd-toc-active' : ''} ${item.level === minLevel ? 'bytemd-toc-first' : ''}`}style={{paddingLeft: `${(item.level - minLevel) * 16 + 8}px`}}onClick={() => handleClick(index)}onKeyDown={(e) => {if (['Enter', 'Space'].includes(e.code)) {handleClick(index); // 监听目录项的点击}}}tabIndex={0} // Make it focusable>{item.text}</li>))}</ul></div>);
};export default Toc;

目录其实就是循环添加<li>标签,当遇到level小一级的,就添加一个缩进;并处理目录项的选中与未选中的样式。

数据层

实现完目录的UI效果后,接下来就是获取目录数据了。因为文章内容是基于markdown语法编写的,所以渲染到页面上时,标题和正文会由不同的标签来区分,我们只需要将其中的<h>标签过滤出来,就能获取到整个文章的目录结构了。

const extractHeadings = () => {if (viewerRef && viewerRef.current) {const headingElements = Array.from(viewerRef.current!.querySelectorAll('h1, h2, h3, h4, h5, h6'));addIdsToHeadings(headingElements)const headingData = headingElements.map((heading) => ({id: heading.id,text: heading.textContent || "",level: parseInt(heading.tagName.replace('H', ''), 10),}));setHeadings(headingData);}
};
function addIdsToHeadings(headingElements: Element[]) {const ids = new Set(); // 用于存储已经生成的ID,确保唯一性let count = 1;headingElements.forEach(heading => {let slug = generateSlug(heading.textContent);let uniqueSlug = slug;// 如果生成的ID已经存在,添加一个计数器来使其唯一while (ids.has(uniqueSlug)) {uniqueSlug = `${slug}-${count++}`;}ids.add(uniqueSlug);heading.id = uniqueSlug;});
}

交互层

然后再处理目录项的点击和滚动事件,点击某一项时页面要滚动到具体的位置(需要根据当前的内容高度动态计算);滚动到某一区域时对应的目录项也要展示被选中的状态

// 处理目录项点击事件
const handleTocClick = (index: number) => {if (viewerRef.current && headings.length > index) {const node = document.getElementById(headings[index].id)if (node == null) {return}// 获取元素当前的位置const elementPosition = node.getBoundingClientRect().top;// 获取当前视窗的滚动位置const currentScrollPosition = scrollableDivRef.current?.scrollTop || 0;// 计算目标位置const targetScrollPosition = currentScrollPosition + elementPosition - OFFSET_TOP;console.log("elementPosition ", elementPosition, "currentScrollPosition ", currentScrollPosition, "targetScrollPosition ", targetScrollPosition)// 滚动到目标位置scrollableDivRef.current?.scrollTo({top: targetScrollPosition,behavior: 'smooth' // 可选,平滑滚动});setTimeout(() => {setCurrentBlockIndex(index)}, 100)}
};const handleScroll = throttle(() => {if (isFromClickRef.current) {return;}if (viewerRef.current) {const headings = viewerRef.current.querySelectorAll('h1, h2, h3, h4, h5, h6');let lastPassedHeadingIndex = 0;for (let i = 0; i < headings.length; i++) {const heading = headings[i];const {top} = heading.getBoundingClientRect();if (top < window.innerHeight * 0.3) {lastPassedHeadingIndex = i;} else {break;}}setCurrentBlockIndex(lastPassedHeadingIndex);}
}, 100);

最后,在需要的位置添加ToC组件即可完成目录的展示啦

<Tochast={headings}currentBlockIndex={currentBlockIndex}onTocClick={handleTocClick}
/>

题外话

也许是由于初始选中组件的原因,整个markdown的开发过程并不算顺利,拓展能力几乎没有,需要自行添加。
同时也还遇到了 其中缩放组件 mediumZoom() 会跟随页面的渲染而重复初始化创建overlay层,导致预览失败。这里也提供一个常用的解决方案:使用useMemo对组件进行处理,使其复用,避免了 mediumZoom()的多次初始化

const viewerComponent = useMemo(() => {return <div ref={viewerRef}><Viewerplugins={plugins}value={articleData.content}/></div>
}, [articleData]);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/37382.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

实验三 时序逻辑电路实验

仿真 链接&#xff1a;https://pan.baidu.com/s/1z9KFQANyNF5PvUPPYFQ9Ow 提取码&#xff1a;e3md 一、实验目的 1、通过实验&#xff0c;理解触发的概念&#xff0c;理解JK、D等常见触发器的功能&#xff1b; 2、通过实验&#xff0c;加深集成计数器功能的理解&#xff0c;掌…

⭐Ollama的本地安装⚡

先来逛一下咱们的主角Ollama的官网地址&#xff1a; Ollama 大概长这个样子&#x1f914; 因为本地系统的原因&#xff0c;文章只提供Widows的安装方式&#xff0c;使用Linux和Mac的大佬&#xff0c;可以自行摸索&#x1f9d0; 下载完成后就是安装了&#x1f355;&#xff0c…

一、Redis简介

一、Redis介绍与一般应用 1.1 基本了解 Redis全称Remote Dictionary Server(远程字典服务)&#xff0c; 是一个开源的高性能键值存储系统&#xff0c;通常用作数据库、缓存和消息代理。使用ANSI C语言编写遵守BSD协议&#xff0c;是一个高性能的Key-Value数据库提供了丰富的数…

JVM性能监控与调优:生产环境的实践指南

JVM性能监控与调优&#xff1a;生产环境的实践指南 一、引言 在生产环境中&#xff0c;Java应用程序的性能监控和调优是确保系统稳定运行、提升用户体验的关键环节。JVM&#xff08;Java Virtual Machine&#xff09;作为Java应用程序的运行环境&#xff0c;其性能直接影响到…

Flink 本地任务添加配置参数

Flink 本地任务添加配置参数 配置一个Configuration&#xff0c;然后通过StreamExecutionEnvironment.getExecutionEnvironment(configuration)传入。 例如&#xff1a; Configuration configuration new Configuration();configuration.set(RestartStrategyOptions.RESTART_…

苹果笔记本能玩网页游戏吗 苹果电脑玩steam游戏怎么样 苹果手机可以玩游戏吗 mac电脑安装windows

苹果笔记本有着优雅的机身、强大的性能&#xff0c;每次更新迭代都备受用户青睐。但是&#xff0c;当需要使用苹果笔记本进行游戏时&#xff0c;很多人会有疑问&#xff1a;苹果笔记本能玩网页游戏吗&#xff1f;苹果笔记本适合打游戏吗&#xff1f;本文将讨论这两个话题&#…

6-14题连接 - 高频 SQL 50 题基础版

目录 1. 相关知识点2. 例子2.6. 使用唯一标识码替换员工ID2.7- 产品销售分析 I2.8 - 进店却未进行过交易的顾客2.9 - 上升的温度2.10 - 每台机器的进程平均运行时间2.11- 员工奖金2.12-学生们参加各科测试的次数2.13-至少有5名直接下属的经理2.14 - 确认率 1. 相关知识点 left …

JavaScript——属性的检测和枚举

目录 任务描述 相关知识 属性的检测 属性的枚举 编程要求 任务描述 本关任务&#xff1a;给定一个属性的名字&#xff0c;请先判断它属于哪一个对象&#xff0c;然后返回该对象的所有自有属性名连接成的字符串。 如&#xff1a;school对象有三个自有属性name,location,s…

达梦数据库系列—15. 表的备份和还原

目录 1、表备份 2、表还原 1、表备份 表备份和表还原恢复&#xff0c;都必须在联机状态下进行。 与备份数据库与表空间不同&#xff0c;不需要备份归档日志&#xff0c;不存在增量备份之说。 CREATE TABLE TAB_FOR_RES_02(C1 INT);CREATE INDEX I_TAB_FOR_RES_02 ON TAB_F…

树状数组——点修区查与区修点查

树状数组是一种代码量小&#xff0c;维护区间的数据结构 他可以实现&#xff1a; 1.区间修改&#xff0c;单点查询 2.单点修改&#xff0c;区间查询 当然&#xff0c;二者不可兼得&#xff0c;大人全都要的话&#xff0c;请选择线段树 前置知识&#xff1a; lowbit(x)操作…

如何安装和配置Monit

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。 关于 Monit Monit 是一个有用的程序&#xff0c;可以自动监控和管理服务器程序&#xff0c;以确保它们不仅保持在线&#xff0c;而且文…

Java与前端框架集成开发指南

Java与前端框架集成开发指南 大家好&#xff0c;我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01; 引言 在当今互联网应用开发中&#xff0c;Java作为一种强大的后端语言&#xff0…

程序人生 - (002)

作为一名程序员&#xff0c;在编程和软件开发的过程中&#xff0c;通常会有一些深刻的感悟和体会。这些感悟不仅仅是关于技术的&#xff0c;也包括对工作的态度、职业的发展和人生的理解。 代码即逻辑&#xff1a;编写代码不仅仅是使用编程语言&#xff0c;更重要的是用逻辑思维…

LDM论文解读

论文名称&#xff1a;High-Resolution Image Synthesis with Latent Diffusion Models 发表时间&#xff1a;CVPR2022 作者及组织&#xff1a;Robin Rombach, Andreas Blattmann, Dominik Lorenz,Patrick Esser和 Bjorn Ommer, 来自Ludwig Maximilian University of Munich &a…

独一无二的设计模式——单例模式(Java实现)

1. 引言 亲爱的读者们&#xff0c;欢迎来到我们的设计模式专题&#xff0c;今天的讲解的设计模式&#xff0c;还是单例模式哦&#xff01;上次讲解的单例模式是基于Python实现&#xff08;独一无二的设计模式——单例模式&#xff08;python实现&#xff09;&#xff09;的&am…

web全屏api,实现元素放大全屏,requestFullscreen,exitFullscreen

全屏api 主要方法 document.exitFullscreen(); 退出页面全屏状态&#xff0c;document是全局文档对象 dom.requestFullscreen(); 使dom进入全屏状态&#xff0c;异步&#xff0c;dom是一个dom元素 dom.onfullscreenchange&#xff08;&#xff09;; 全…

专题四:Spring源码初始化环境与BeanFactory

上文我们通过new ClassPathXmlApplicationContext("applicationContext.xml");这段代码看了下Spring是如何将Xml里面内容注入到Java对象中&#xff0c;并通过context.getBean("jmUser");方式获得了一个对象实例&#xff0c;而避开使用new 来耦合。今天我们…

【TB作品】智能台灯控制器,ATMEGA128单片机,Proteus仿真

题目 8 &#xff1a;智能台灯控制器 基于单片机设计智能台灯控制器&#xff0c;要求可以调节 LED 灯的亮度&#xff0c;实现定时开启与关闭&#xff0c; 根据光照自动开启与关闭功能。 具体要求如下&#xff1a; &#xff08;1&#xff09;通过 PWM 功能调节 LED 灯亮度&#x…

【本地调试】使用 Nginx 和 Hosts 文件实现本地开发调试请求转发

可以按照以下 nginx 配置来设置&#xff0c;通过 nginx 和 host 将网页的请求转发到本地的后端服务器&#xff0c;以方便本地开发调试 一、nginx 配置 worker_processes 1;events {worker_connections 1024; }http {include mime.types;default_type application/js…

【Python】 数据分析中的常见统计量:中位数

那年夏天我和你躲在 这一大片宁静的海 直到后来我们都还在 对这个世界充满期待 今年冬天你已经不在 我的心空出了一块 很高兴遇见你 让我终究明白 回忆比真实精彩 &#x1f3b5; 王心凌《那年夏天宁静的海》 中位数&#xff08;Median&#xff09;是统计学…