From d7294628880c4bcfe9a453e610e336a93552d522 Mon Sep 17 00:00:00 2001 From: Ashminita Date: Wed, 3 Jun 2026 16:10:37 +0530 Subject: [PATCH 01/15] fix(coderabbit): remove space in path declaration --- .coderabbit.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.coderabbit.yaml b/.coderabbit.yaml index 8c61ecc..7ad8191 100644 --- a/.coderabbit.yaml +++ b/.coderabbit.yaml @@ -113,7 +113,7 @@ reviews: - Markdown prose must remain readable for tables, code, blockquotes, links, lists, and images. - Prefer existing tokens/classes over ad hoc inline styling. - - path: 'packages/shared-* /src/**/*.ts' + - path: 'packages/shared-*/src/**/*.ts' instructions: | Review shared package contracts. - IPC constants, menu constants, shortcuts, and shared types are public contracts. From 2f359bc8b358a697452d023f1ab3e487ca946068 Mon Sep 17 00:00:00 2001 From: Ashminita Date: Wed, 3 Jun 2026 16:16:30 +0530 Subject: [PATCH 02/15] fix(exportPDF): avoid loading large base64 inlined HTML via a data: URL in exportPDF --- apps/main-processor/src/export/exportPdf.ts | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/apps/main-processor/src/export/exportPdf.ts b/apps/main-processor/src/export/exportPdf.ts index 1912017..f8ad47b 100644 --- a/apps/main-processor/src/export/exportPdf.ts +++ b/apps/main-processor/src/export/exportPdf.ts @@ -1,5 +1,7 @@ import { BrowserWindow } from 'electron'; -import { writeFile } from 'node:fs/promises'; +import { writeFile, rm } from 'node:fs/promises'; +import { join } from 'node:path'; +import { tmpdir } from 'node:os'; import { buildDocument } from './buildDocument'; import { sanitizeCss } from './sanitizeCss'; import { inlineImages } from './inlineImage'; @@ -11,12 +13,16 @@ export async function exportPDF(bodyHtml: string, css: string, outputPath: strin sandbox: true, }, }); - + const tempFilePath = join( + tmpdir(), + `pdf-export-${Date.now()}-${Math.random().toString(36).slice(2, 9)}.html` + ); try { const htmlWithInlineImages = await inlineImages(bodyHtml); const html = buildDocument(htmlWithInlineImages, sanitizeCss(css)); - await pdfWindow.loadURL(`data:text/html;charset=utf-8,${encodeURIComponent(html)}`); + await writeFile(tempFilePath, html, 'utf8'); + await pdfWindow.loadFile(tempFilePath); await pdfWindow.webContents.executeJavaScript(` new Promise((resolve) => { @@ -42,5 +48,10 @@ export async function exportPDF(bodyHtml: string, css: string, outputPath: strin await writeFile(outputPath, pdfBuffer); } finally { pdfWindow.close(); + try { + await rm(tempFilePath, { force: true }); + } catch (error) { + console.error('Failed to clean up PDF export temp file:', error); + } } } From d752aa6713e8d2191c40d3d6f5746a9edc3704bf Mon Sep 17 00:00:00 2001 From: Ashminita Date: Wed, 3 Jun 2026 16:22:20 +0530 Subject: [PATCH 03/15] fix(ipc): remove hardcoded slash for folder search --- apps/main-processor/src/ipc.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/main-processor/src/ipc.ts b/apps/main-processor/src/ipc.ts index 76c28ee..1d86db9 100644 --- a/apps/main-processor/src/ipc.ts +++ b/apps/main-processor/src/ipc.ts @@ -1,4 +1,5 @@ import { app, ipcMain, dialog } from 'electron'; +import { sep } from 'node:path'; import { readFile, unWatchFile, watchFile } from './file'; import { getFolder } from './folder'; import { @@ -209,7 +210,7 @@ export function registerIPCHandlers(): void { } const safeFolderPath = await resolveDirectoryPath(folderPath); const isAllowed = Array.from(allowedFolderRoots).some( - (root) => safeFolderPath === root || safeFolderPath.startsWith(`${root}/`) + (root) => safeFolderPath === root || safeFolderPath.startsWith(`${root}${sep}`) ); if (!isAllowed) { throw new Error('Folder path is not authorized'); From 88e0ac49269b519fe34d15ca2d274bf350f840f1 Mon Sep 17 00:00:00 2001 From: Ashminita Date: Wed, 3 Jun 2026 16:24:51 +0530 Subject: [PATCH 04/15] feat(menu): add checked option in theme switch in window --- apps/main-processor/src/index.ts | 2 +- apps/main-processor/src/menu.ts | 3 ++- apps/main-processor/src/register-menu.ts | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/apps/main-processor/src/index.ts b/apps/main-processor/src/index.ts index 422f9c3..2fce901 100644 --- a/apps/main-processor/src/index.ts +++ b/apps/main-processor/src/index.ts @@ -97,7 +97,7 @@ if (!hasSingleInstanceLock) { // electron ready window app.whenReady().then(() => { - registerMenu(); + registerMenu('github-light'); createWindow(); //re create window when dock icon clicked in macOs diff --git a/apps/main-processor/src/menu.ts b/apps/main-processor/src/menu.ts index 061453f..fb9b19f 100644 --- a/apps/main-processor/src/menu.ts +++ b/apps/main-processor/src/menu.ts @@ -2,7 +2,7 @@ import type { MenuItemConstructorOptions } from 'electron'; import { MENU_EVENTS, MENU_LABELS, SHORTCUTS, THEMES } from '@package/shared-constants'; import { createMenuSender } from './utils/helper/menu-helper'; -export function buildMenuTemplate(): MenuItemConstructorOptions[] { +export function buildMenuTemplate(currentTheme: string): MenuItemConstructorOptions[] { const send = createMenuSender; return [ @@ -75,6 +75,7 @@ export function buildMenuTemplate(): MenuItemConstructorOptions[] { submenu: THEMES.map((theme) => ({ label: theme, type: 'radio' as const, + checked: theme === currentTheme, click: send(MENU_EVENTS.SET_THEME, theme), })), }, diff --git a/apps/main-processor/src/register-menu.ts b/apps/main-processor/src/register-menu.ts index b57d678..5565d48 100644 --- a/apps/main-processor/src/register-menu.ts +++ b/apps/main-processor/src/register-menu.ts @@ -1,7 +1,7 @@ import { Menu } from 'electron'; import { buildMenuTemplate } from './menu'; -export function registerMenu(): void { - const menu = Menu.buildFromTemplate(buildMenuTemplate()); +export function registerMenu(currentTheme: string): void { + const menu = Menu.buildFromTemplate(buildMenuTemplate(currentTheme)); Menu.setApplicationMenu(menu); } From a0c8999ea7c84e92f86fa1580273b03f5e3845af Mon Sep 17 00:00:00 2001 From: Ashminita Date: Wed, 3 Jun 2026 16:28:12 +0530 Subject: [PATCH 05/15] fix(settings): avoid array values from passing object check --- apps/main-processor/src/settings/get-settings.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/main-processor/src/settings/get-settings.ts b/apps/main-processor/src/settings/get-settings.ts index 0204a21..01763ac 100644 --- a/apps/main-processor/src/settings/get-settings.ts +++ b/apps/main-processor/src/settings/get-settings.ts @@ -9,7 +9,7 @@ export async function getSettings(): Promise { try { const data = await readFile(settingsPath, 'utf-8'); const parsed = JSON.parse(data); - if (parsed && typeof parsed === 'object') { + if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) { return { ...DEFAULT_SETTINGS, ...validateSettings(parsed) }; } return DEFAULT_SETTINGS; From 2bea6fcd221cf97a737e384b576c152a46471335 Mon Sep 17 00:00:00 2001 From: Ashminita Date: Wed, 3 Jun 2026 16:29:57 +0530 Subject: [PATCH 06/15] fix(docs): remove duplicate heading --- docs/docs/architecture.md | 2 -- docs/versioned_docs/version-1.0.0/architecture.md | 2 -- 2 files changed, 4 deletions(-) diff --git a/docs/docs/architecture.md b/docs/docs/architecture.md index 365d2d9..3238262 100644 --- a/docs/docs/architecture.md +++ b/docs/docs/architecture.md @@ -2,8 +2,6 @@ title: Architecture --- -# Architecture - # Architecture Overview ## 1. System Communication Flow diff --git a/docs/versioned_docs/version-1.0.0/architecture.md b/docs/versioned_docs/version-1.0.0/architecture.md index 365d2d9..3238262 100644 --- a/docs/versioned_docs/version-1.0.0/architecture.md +++ b/docs/versioned_docs/version-1.0.0/architecture.md @@ -2,8 +2,6 @@ title: Architecture --- -# Architecture - # Architecture Overview ## 1. System Communication Flow From f828d9a520310187d7f000109050dd9ac2ad97f5 Mon Sep 17 00:00:00 2001 From: Ashminita Date: Wed, 3 Jun 2026 16:30:46 +0530 Subject: [PATCH 07/15] fix(shiki): handle shiki error --- apps/renderer/src/renderer/shiki.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/renderer/src/renderer/shiki.ts b/apps/renderer/src/renderer/shiki.ts index bdfb94e..2fd89c0 100644 --- a/apps/renderer/src/renderer/shiki.ts +++ b/apps/renderer/src/renderer/shiki.ts @@ -18,6 +18,9 @@ export async function shikiHighlighter(): Promise { themes, langs, engine: createJavaScriptRegexEngine(), + }).catch((err) => { + highlighter = null; + throw err; }); } return highlighter; From 6f50eecfb386ea806bc40bb6a40b868d4d0285f6 Mon Sep 17 00:00:00 2001 From: Ashminita Date: Wed, 3 Jun 2026 16:35:32 +0530 Subject: [PATCH 08/15] fix(toc): fix TOC anchor IDs when headings repeat --- apps/renderer/src/App.tsx | 2 +- apps/renderer/src/renderer/markdown.ts | 2 ++ apps/renderer/src/renderer/toc.ts | 16 ++++++++-------- .../renderer/src/utils/helpers/heading-helper.ts | 12 +++++++++++- 4 files changed, 22 insertions(+), 10 deletions(-) diff --git a/apps/renderer/src/App.tsx b/apps/renderer/src/App.tsx index 00cf48f..9c09b32 100644 --- a/apps/renderer/src/App.tsx +++ b/apps/renderer/src/App.tsx @@ -198,7 +198,7 @@ useShortcuts({ )} {!focusMode && ( { + resetHeadingRegistry(); if (!markdownText || markdownText.trim() === '') { return ''; } diff --git a/apps/renderer/src/renderer/toc.ts b/apps/renderer/src/renderer/toc.ts index 2c6db4f..067b3fc 100644 --- a/apps/renderer/src/renderer/toc.ts +++ b/apps/renderer/src/renderer/toc.ts @@ -1,11 +1,16 @@ import { lexer } from 'marked'; import { TOCType } from '../types/component-types'; -import { getHeadingId, isHeadingToken, headingText } from '../utils/helpers/heading-helper'; +import { + getHeadingId, + isHeadingToken, + headingText, + resetHeadingRegistry, +} from '../utils/helpers/heading-helper'; // extracts table of content from HTML string export function extractTOC(html: string): TOCType[] { const items: TOCType[] = []; - const idCount = new Map(); + resetHeadingRegistry(); const tokens = lexer(html); for (const token of tokens) { @@ -14,12 +19,7 @@ export function extractTOC(html: string): TOCType[] { const level = Math.min(token.depth, 3) as 1 | 2 | 3; const text = headingText(token); if (!text) continue; - const firstid = getHeadingId(text); - - const count = idCount.get(firstid) ?? 0; - idCount.set(firstid, count + 1); - const id = count === 0 ? firstid : `${firstid}-${count}`; - + const id = getHeadingId(text); items.push({ id, text, level }); } return items; diff --git a/apps/renderer/src/utils/helpers/heading-helper.ts b/apps/renderer/src/utils/helpers/heading-helper.ts index c9dd5de..f510515 100644 --- a/apps/renderer/src/utils/helpers/heading-helper.ts +++ b/apps/renderer/src/utils/helpers/heading-helper.ts @@ -2,6 +2,13 @@ import { HeadingProps } from '../../types/component-types'; import { SLUG_PATTERNS, HTML_PATTERNS } from '../constants/regex-constants'; import { parseInline, type Tokens } from 'marked'; +const idRegistry = new Map(); + +//Resets the shared heading counter state. +export function resetHeadingRegistry(): void { + idRegistry.clear(); +} + // converts heading text into a ID export function getHeadingId(text: string): string { let id = text.toLowerCase(); @@ -9,7 +16,10 @@ export function getHeadingId(text: string): string { id = id.replace(SLUG_PATTERNS.SPACES, '-'); id = id.trim().replace(SLUG_PATTERNS.TRIM_HYPHENS, ''); - return id; + const count = idRegistry.get(id) || 0; + idRegistry.set(id, count + 1); + + return count === 0 ? id : `${id}-${count}`; } // assigns id to the headings From 3930380b28d90efefbbd6fd548f56f0b547f513c Mon Sep 17 00:00:00 2001 From: Ashminita Date: Wed, 3 Jun 2026 16:37:29 +0530 Subject: [PATCH 09/15] fix(renderer): add window api guard --- apps/renderer/src/hooks/useFileActions.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/renderer/src/hooks/useFileActions.ts b/apps/renderer/src/hooks/useFileActions.ts index 0eb1426..8e371d3 100644 --- a/apps/renderer/src/hooks/useFileActions.ts +++ b/apps/renderer/src/hooks/useFileActions.ts @@ -32,6 +32,7 @@ export function useFileActions({ loadFile, dispatch }: FileActionProps) { ); const openFileDialog = useCallback(() => { + if (!window.api) return; void window.api.openFileDialog().then((chosenPath) => { if (chosenPath) { void loadFileInTab(chosenPath); From 82f2a2a85a4115e5f33c188696763e603a05d500 Mon Sep 17 00:00:00 2001 From: Ashminita Date: Wed, 3 Jun 2026 17:11:23 +0530 Subject: [PATCH 10/15] fix(updater): prevent autoUpdater listener duplication when recreating the main window --- apps/main-processor/src/updater.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/main-processor/src/updater.ts b/apps/main-processor/src/updater.ts index f95b69e..f5771dc 100644 --- a/apps/main-processor/src/updater.ts +++ b/apps/main-processor/src/updater.ts @@ -6,6 +6,10 @@ export function setupAutoUpdater(window: BrowserWindow): void { autoUpdater.autoDownload = false; autoUpdater.autoInstallOnAppQuit = true; + autoUpdater.removeAllListeners('update-available'); + autoUpdater.removeAllListeners('update-downloaded'); + autoUpdater.removeAllListeners('error'); + autoUpdater.on('update-available', (info: UpdateInfo) => { window.webContents.send(IPC_CONSTANTS.UPDATE_AVAILABLE, info.version); }); From 5ee99b9461367d6d8b6762668fe3d2c87a6f4d6d Mon Sep 17 00:00:00 2001 From: Ashminita Date: Wed, 3 Jun 2026 17:12:44 +0530 Subject: [PATCH 11/15] fix(index): extend content policy --- apps/renderer/index.html | 2 +- apps/renderer/src/App.tsx | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/renderer/index.html b/apps/renderer/index.html index 406194a..e35395f 100644 --- a/apps/renderer/index.html +++ b/apps/renderer/index.html @@ -9,7 +9,7 @@ diff --git a/apps/renderer/src/App.tsx b/apps/renderer/src/App.tsx index 9c09b32..ce554f6 100644 --- a/apps/renderer/src/App.tsx +++ b/apps/renderer/src/App.tsx @@ -13,7 +13,6 @@ import { SearchBar } from './components/SearchBar'; import { useSettings } from './hooks/useSettings'; import { StatusBar } from './components/StatusBar'; import { FileBrowser } from './components/FileBrowser'; -import { extractTOC } from './renderer/toc'; import { TabBar } from './components/TabBar'; import { useTabStore } from './hooks/useTabStore'; import { Icons } from './utils/constants/icon-contants'; From 28929c9386ffe439fb51dd2658fcf3edcc89bb29 Mon Sep 17 00:00:00 2001 From: Ashminita Date: Wed, 3 Jun 2026 17:19:31 +0530 Subject: [PATCH 12/15] fix(ipc-validation): remove window path check from only C: drive to all drive --- .../src/utils/constants/ipc-validation.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/apps/main-processor/src/utils/constants/ipc-validation.ts b/apps/main-processor/src/utils/constants/ipc-validation.ts index 13d2238..b48925d 100644 --- a/apps/main-processor/src/utils/constants/ipc-validation.ts +++ b/apps/main-processor/src/utils/constants/ipc-validation.ts @@ -39,12 +39,13 @@ export function validatePath(filePath: string) { // Prevent access to sensitive OS system folders const lower = resolvedPath.toLowerCase(); if (process.platform === 'win32') { + const sysDrive = (process.env.SystemDrive ?? 'C:').toLowerCase(); const forbiddenPrefixes = [ - 'c:\\windows\\', - 'c:\\winnt\\', - 'c:\\boot\\', - 'c:\\system volume information\\', - 'c:\\$recycle.bin\\', + `${sysDrive}\\windows\\`, + `${sysDrive}\\winnt\\`, + `${sysDrive}\\boot\\`, + `${sysDrive}\\system volume information\\`, + `${sysDrive}\\$recycle.bin\\`, ]; if (forbiddenPrefixes.some((p) => lower === p.slice(0, -1) || lower.startsWith(p))) { return false; From 04a8a48b86efa711225e58052d0def922c99dd9e Mon Sep 17 00:00:00 2001 From: Ashminita Date: Wed, 3 Jun 2026 17:31:43 +0530 Subject: [PATCH 13/15] fix(renderer): prevent post unmount state updates in useFilePersistence --- apps/renderer/src/hooks/useFilePersistence.ts | 30 +++++++++++++++---- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/apps/renderer/src/hooks/useFilePersistence.ts b/apps/renderer/src/hooks/useFilePersistence.ts index 113cde6..86a3c5c 100644 --- a/apps/renderer/src/hooks/useFilePersistence.ts +++ b/apps/renderer/src/hooks/useFilePersistence.ts @@ -10,18 +10,35 @@ export function useFilePersistence({ contentRef, setShowToast, }: FilePersistenceProps) { - const debounceTimer = useRef(undefined); - const scrollTimer = useRef(undefined); + const debounceTimer = useRef | undefined>(undefined); + const scrollTimer = useRef | undefined>(undefined); + const isMounted = useRef(true); + + useEffect(() => { + isMounted.current = true; + return () => { + isMounted.current = false; + if (debounceTimer.current) { + window.clearTimeout(debounceTimer.current); + debounceTimer.current = undefined; + } + if (scrollTimer.current) { + window.clearTimeout(scrollTimer.current); + scrollTimer.current = undefined; + } + }; + }, []); const handleFileChange = useCallback(() => { if (!activeTab) return; if (debounceTimer.current) { - clearTimeout(debounceTimer.current); + window.clearTimeout(debounceTimer.current); } debounceTimer.current = window.setTimeout(async () => { + if (!debounceTimer.current) return; const currentScroll = contentRef.current?.scrollTop ?? 0; const result = await loadFile(activeTab.filePath); - if (!result) return; + if (!result || !isMounted.current) return; dispatch({ type: 'UPDATE_TAB_STATE', payload: { @@ -43,10 +60,11 @@ export function useFilePersistence({ if (!activeTab || !contentRef.current) return; if (scrollTimer.current) { - clearTimeout(scrollTimer.current); + window.clearTimeout(scrollTimer.current); } scrollTimer.current = window.setTimeout(() => { + if (!scrollTimer.current || !contentRef.current) return; saveScrollPos(activeTab.filePath, contentRef.current!.scrollTop); dispatch({ @@ -67,7 +85,7 @@ export function useFilePersistence({ contentRef.current.scrollTop = activeTab.scrollTop ?? getScrollPos(activeTab.filePath); } }); - }, [activeTab?.id, activeTab?.html]); + }, [activeTab?.id, activeTab?.html, contentRef]); return { scroll }; } From 15495b7a804f3f87b941d2b3f08859437c58dd19 Mon Sep 17 00:00:00 2001 From: Ashminita Date: Wed, 3 Jun 2026 17:49:24 +0530 Subject: [PATCH 14/15] fix(export): wait for the browser window to close before deleting the temp file --- apps/main-processor/src/export/exportPdf.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/main-processor/src/export/exportPdf.ts b/apps/main-processor/src/export/exportPdf.ts index f8ad47b..4f4613e 100644 --- a/apps/main-processor/src/export/exportPdf.ts +++ b/apps/main-processor/src/export/exportPdf.ts @@ -1,3 +1,4 @@ +import { once } from 'node:events'; import { BrowserWindow } from 'electron'; import { writeFile, rm } from 'node:fs/promises'; import { join } from 'node:path'; @@ -47,7 +48,13 @@ export async function exportPDF(bodyHtml: string, css: string, outputPath: strin }); await writeFile(outputPath, pdfBuffer); } finally { - pdfWindow.close(); + const closed = pdfWindow.isDestroyed() + ? Promise.resolve() + : once(pdfWindow, 'closed').then(() => undefined); + if (!pdfWindow.isDestroyed()) { + pdfWindow.close(); + } + await closed; try { await rm(tempFilePath, { force: true }); } catch (error) { From 0c5992fc0aa17adc1e573a55469810d9cdace7c3 Mon Sep 17 00:00:00 2001 From: Ashminita Date: Wed, 3 Jun 2026 18:34:15 +0530 Subject: [PATCH 15/15] fix(renderer): fix shared global heading id state across render passes --- apps/renderer/src/config/marked.ts | 13 +++++++------ apps/renderer/src/renderer/markdown.ts | 7 ++++--- apps/renderer/src/renderer/toc.ts | 6 +++--- .../src/utils/helpers/heading-helper.ts | 17 +++++++---------- 4 files changed, 21 insertions(+), 22 deletions(-) diff --git a/apps/renderer/src/config/marked.ts b/apps/renderer/src/config/marked.ts index 796d4ac..f4de237 100644 --- a/apps/renderer/src/config/marked.ts +++ b/apps/renderer/src/config/marked.ts @@ -5,17 +5,18 @@ import { THEMES } from '@package/shared-constants'; import { escapeHtml, heading } from '../utils/helpers/heading-helper'; import { MARKDOWN_LANGUAGES } from '../utils/constants/markdown-constants'; -let instance: Marked | null = null; - -export function getMarkdown(): Marked { - if (instance) return instance; - instance = new Marked(); +export function getMarkdown(registry: Map): Marked { + const instance = new Marked(); // configure marked with GFM options instance.use({ gfm: true, breaks: false, - renderer: { heading }, + renderer: { + heading(props) { + return heading(props, registry); + }, + }, }); //configure marked to use Shikhi for code blocks diff --git a/apps/renderer/src/renderer/markdown.ts b/apps/renderer/src/renderer/markdown.ts index 8a99368..5ff6a0b 100644 --- a/apps/renderer/src/renderer/markdown.ts +++ b/apps/renderer/src/renderer/markdown.ts @@ -1,15 +1,16 @@ import { getMarkdown } from '../config/marked'; -import { resetHeadingRegistry } from '../utils/helpers/heading-helper'; +import { createHeadingRegistry } from '../utils/helpers/heading-helper'; import { parseCallouts } from './callout'; // converts markdown text into plain HTML string export async function renderMarkdown(markdownText: string): Promise { - resetHeadingRegistry(); + const registry = createHeadingRegistry(); + if (!markdownText || markdownText.trim() === '') { return ''; } - const marked = getMarkdown(); + const marked = getMarkdown(registry); let result = await marked.parse(markdownText); if (result.includes('$')) { const { processAllMath } = await import('./katex'); diff --git a/apps/renderer/src/renderer/toc.ts b/apps/renderer/src/renderer/toc.ts index 067b3fc..2d3c930 100644 --- a/apps/renderer/src/renderer/toc.ts +++ b/apps/renderer/src/renderer/toc.ts @@ -4,13 +4,13 @@ import { getHeadingId, isHeadingToken, headingText, - resetHeadingRegistry, + createHeadingRegistry, } from '../utils/helpers/heading-helper'; // extracts table of content from HTML string export function extractTOC(html: string): TOCType[] { const items: TOCType[] = []; - resetHeadingRegistry(); + const registry = createHeadingRegistry(); const tokens = lexer(html); for (const token of tokens) { @@ -19,7 +19,7 @@ export function extractTOC(html: string): TOCType[] { const level = Math.min(token.depth, 3) as 1 | 2 | 3; const text = headingText(token); if (!text) continue; - const id = getHeadingId(text); + const id = getHeadingId(text, registry); items.push({ id, text, level }); } return items; diff --git a/apps/renderer/src/utils/helpers/heading-helper.ts b/apps/renderer/src/utils/helpers/heading-helper.ts index f510515..66b5e72 100644 --- a/apps/renderer/src/utils/helpers/heading-helper.ts +++ b/apps/renderer/src/utils/helpers/heading-helper.ts @@ -2,30 +2,27 @@ import { HeadingProps } from '../../types/component-types'; import { SLUG_PATTERNS, HTML_PATTERNS } from '../constants/regex-constants'; import { parseInline, type Tokens } from 'marked'; -const idRegistry = new Map(); - -//Resets the shared heading counter state. -export function resetHeadingRegistry(): void { - idRegistry.clear(); +export function createHeadingRegistry(): Map { + return new Map(); } // converts heading text into a ID -export function getHeadingId(text: string): string { +export function getHeadingId(text: string, registry: Map): string { let id = text.toLowerCase(); id = id.replace(SLUG_PATTERNS.NON_WORD, ''); id = id.replace(SLUG_PATTERNS.SPACES, '-'); id = id.trim().replace(SLUG_PATTERNS.TRIM_HYPHENS, ''); - const count = idRegistry.get(id) || 0; - idRegistry.set(id, count + 1); + const count = registry.get(id) || 0; + registry.set(id, count + 1); return count === 0 ? id : `${id}-${count}`; } // assigns id to the headings -export function heading({ text, depth }: HeadingProps) { +export function heading({ text, depth }: HeadingProps, registry: Map) { const plainText = stripHtml(text); - const id = getHeadingId(plainText); + const id = getHeadingId(plainText, registry); const safeText = escapeHtml(plainText); return `${safeText}\n`; }