feat: add lazy table of contents panel
This commit is contained in:
74
src/components/tableOfContentsWorkerClient.ts
Normal file
74
src/components/tableOfContentsWorkerClient.ts
Normal 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)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user