import { buildTableOfContentsFromMarkdownOnly, type TocItem } from './tableOfContentsModel' export const TOC_BUILD_DEBOUNCE_MS = 180 interface WorkerRequest { entryTitle: string markdown: string requestId: number } interface WorkerResponse { requestId: number toc: TocItem } interface PendingRequest { reject: (reason?: unknown) => void resolve: (toc: TocItem) => void } let nextRequestId = 1 let tocWorker: Worker | null = null const pendingRequests = new Map() function rejectPendingRequests(reason: unknown) { pendingRequests.forEach(({ reject }) => reject(reason)) pendingRequests.clear() } function buildTocWithoutWorker(entryTitle: string, markdown: string): Promise { return new Promise((resolve) => { window.setTimeout(() => { resolve(buildTableOfContentsFromMarkdownOnly(entryTitle, markdown)) }, 0) }) } function handleWorkerMessage(event: MessageEvent) { const pending = pendingRequests.get(event.data.requestId) if (!pending) return pendingRequests.delete(event.data.requestId) pending.resolve(event.data.toc) } function handleWorkerError(event: ErrorEvent) { rejectPendingRequests(event.error ?? event.message) tocWorker?.terminate() tocWorker = null } function activeTocWorker(): Worker | null { if (typeof Worker === 'undefined') return null if (tocWorker) return tocWorker tocWorker = new Worker(new URL('./tableOfContents.worker.ts', import.meta.url), { type: 'module' }) tocWorker.onmessage = handleWorkerMessage tocWorker.onerror = handleWorkerError return tocWorker } export function buildTableOfContentsInWorker(entryTitle: string, markdown: string): Promise { const worker = activeTocWorker() if (!worker) return buildTocWithoutWorker(entryTitle, markdown) return new Promise((resolve, reject) => { const requestId = nextRequestId nextRequestId += 1 pendingRequests.set(requestId, { resolve, reject }) const request: WorkerRequest = { entryTitle, markdown, requestId } worker.postMessage(request) }) }