feat: add lazy table of contents panel

This commit is contained in:
lucaronin
2026-05-04 12:37:30 +02:00
parent 582d1d25d6
commit 71ef8325a0
13 changed files with 914 additions and 438 deletions

View File

@@ -0,0 +1,74 @@
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<number, PendingRequest>()
function rejectPendingRequests(reason: unknown) {
pendingRequests.forEach(({ reject }) => reject(reason))
pendingRequests.clear()
}
function buildTocWithoutWorker(entryTitle: string, markdown: string): Promise<TocItem> {
return new Promise((resolve) => {
window.setTimeout(() => {
resolve(buildTableOfContentsFromMarkdownOnly(entryTitle, markdown))
}, 0)
})
}
function handleWorkerMessage(event: MessageEvent<WorkerResponse>) {
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<TocItem> {
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)
})
}