我是如何在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.pswp.cn/web/37382.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/37382.shtml
英文地址,請注明出處:http://en.pswp.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;是統計學…