2024-10-11 05:24:17 -04:00
|
|
|
/* eslint-disable no-dupe-class-members */
|
|
|
|
|
2024-10-02 04:25:02 -04:00
|
|
|
import { v4 as uuid } from 'uuid'
|
|
|
|
import getMeta from '@/utils/meta'
|
|
|
|
import { debugConsole } from '@/utils/debugging'
|
2024-10-11 05:24:17 -04:00
|
|
|
import { captureException } from '@/infrastructure/error-reporter'
|
2024-10-02 04:25:02 -04:00
|
|
|
|
2024-10-11 05:24:17 -04:00
|
|
|
type SpellMessage = {
|
|
|
|
type: 'spell'
|
|
|
|
words: string[]
|
|
|
|
}
|
|
|
|
|
|
|
|
type SuggestMessage = {
|
|
|
|
type: 'suggest'
|
|
|
|
word: string
|
|
|
|
}
|
|
|
|
|
|
|
|
type AddWordMessage = {
|
|
|
|
type: 'add_word'
|
|
|
|
word: string
|
|
|
|
}
|
|
|
|
|
|
|
|
type RemoveWordMessage = {
|
|
|
|
type: 'remove_word'
|
|
|
|
word: string
|
|
|
|
}
|
|
|
|
|
|
|
|
type DestroyMessage = {
|
|
|
|
type: 'destroy'
|
|
|
|
}
|
|
|
|
|
|
|
|
type Message = { id?: string } & (
|
|
|
|
| SpellMessage
|
|
|
|
| SuggestMessage
|
|
|
|
| AddWordMessage
|
|
|
|
| RemoveWordMessage
|
|
|
|
| DestroyMessage
|
|
|
|
)
|
|
|
|
|
|
|
|
type EmptyResult = Record<string, never>
|
|
|
|
|
|
|
|
type ErrorResult = {
|
|
|
|
error: true
|
|
|
|
}
|
|
|
|
|
|
|
|
type SpellResult = {
|
|
|
|
misspellings: { index: number }[]
|
|
|
|
}
|
|
|
|
|
|
|
|
type SuggestResult = {
|
|
|
|
suggestions: string[]
|
|
|
|
}
|
|
|
|
|
|
|
|
type ResultCallback =
|
|
|
|
| ((value: SpellResult | ErrorResult) => void)
|
|
|
|
| ((value: SuggestResult | ErrorResult) => void)
|
|
|
|
| ((value: EmptyResult | ErrorResult) => void)
|
2024-10-02 04:25:02 -04:00
|
|
|
|
|
|
|
export class HunspellManager {
|
2024-10-08 04:24:44 -04:00
|
|
|
baseAssetPath: string
|
2024-10-02 04:25:02 -04:00
|
|
|
dictionariesRoot: string
|
|
|
|
hunspellWorker!: Worker
|
|
|
|
abortController: AbortController | undefined
|
|
|
|
listening = false
|
|
|
|
loaded = false
|
2024-10-11 05:24:17 -04:00
|
|
|
loadingFailed = false
|
2024-10-02 04:25:02 -04:00
|
|
|
pendingMessages: Message[] = []
|
2024-10-11 05:24:17 -04:00
|
|
|
callbacks: Map<string, ResultCallback> = new Map()
|
2024-10-02 04:25:02 -04:00
|
|
|
|
|
|
|
constructor(
|
|
|
|
private readonly language: string,
|
|
|
|
private readonly learnedWords: string[]
|
|
|
|
) {
|
2024-10-08 04:24:44 -04:00
|
|
|
this.baseAssetPath = new URL(
|
2024-10-02 04:25:02 -04:00
|
|
|
getMeta('ol-baseAssetPath'),
|
|
|
|
window.location.href
|
|
|
|
).toString()
|
|
|
|
|
2024-10-08 04:24:44 -04:00
|
|
|
this.dictionariesRoot = getMeta('ol-dictionariesRoot')
|
|
|
|
|
2024-10-09 04:20:06 -04:00
|
|
|
this.hunspellWorker = new Worker(
|
|
|
|
/* webpackChunkName: "hunspell-worker" */
|
|
|
|
new URL('./hunspell.worker.ts', import.meta.url),
|
|
|
|
{ type: 'module' }
|
|
|
|
)
|
2024-10-02 04:25:02 -04:00
|
|
|
|
2024-10-09 04:20:06 -04:00
|
|
|
this.hunspellWorker.addEventListener('message', this.receive.bind(this))
|
2024-10-02 04:25:02 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
destroy() {
|
|
|
|
this.send({ type: 'destroy' }, () => {
|
|
|
|
this.hunspellWorker.terminate()
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-10-11 05:24:17 -04:00
|
|
|
send(
|
|
|
|
message: AddWordMessage,
|
|
|
|
callback: (value: EmptyResult | ErrorResult) => void
|
|
|
|
): void
|
|
|
|
|
|
|
|
send(
|
|
|
|
message: RemoveWordMessage,
|
|
|
|
callback: (value: EmptyResult | ErrorResult) => void
|
|
|
|
): void
|
|
|
|
|
|
|
|
send(
|
|
|
|
message: DestroyMessage,
|
|
|
|
callback: (value: EmptyResult | ErrorResult) => void
|
|
|
|
): void
|
|
|
|
|
|
|
|
send(
|
|
|
|
message: SuggestMessage,
|
|
|
|
callback: (value: SuggestResult | ErrorResult) => void
|
|
|
|
): void
|
|
|
|
|
|
|
|
send(
|
|
|
|
message: SpellMessage,
|
|
|
|
callback: (value: SpellResult | ErrorResult) => void
|
|
|
|
): void
|
|
|
|
|
|
|
|
send(message: Message, callback: ResultCallback): void {
|
|
|
|
if (this.loadingFailed) {
|
|
|
|
return // ignore the message
|
|
|
|
}
|
|
|
|
|
2024-10-02 04:25:02 -04:00
|
|
|
if (callback) {
|
|
|
|
message.id = uuid()
|
|
|
|
this.callbacks.set(message.id, callback)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this.listening) {
|
|
|
|
this.hunspellWorker.postMessage(message)
|
|
|
|
} else {
|
|
|
|
this.pendingMessages.push(message)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
receive(event: MessageEvent) {
|
|
|
|
debugConsole.log(event.data)
|
2024-10-11 05:24:17 -04:00
|
|
|
const { id, ...rest } = event.data
|
2024-10-02 04:25:02 -04:00
|
|
|
if (id) {
|
|
|
|
const callback = this.callbacks.get(id)
|
|
|
|
if (callback) {
|
|
|
|
this.callbacks.delete(id)
|
|
|
|
callback(rest)
|
|
|
|
}
|
2024-10-11 05:24:17 -04:00
|
|
|
} else if (rest.listening) {
|
2024-10-02 04:25:02 -04:00
|
|
|
this.listening = true
|
|
|
|
this.hunspellWorker.postMessage({
|
|
|
|
type: 'init',
|
|
|
|
lang: this.language,
|
|
|
|
learnedWords: this.learnedWords, // TODO: add words
|
2024-10-08 04:24:44 -04:00
|
|
|
baseAssetPath: this.baseAssetPath,
|
2024-10-02 04:25:02 -04:00
|
|
|
dictionariesRoot: this.dictionariesRoot,
|
|
|
|
})
|
|
|
|
for (const message of this.pendingMessages) {
|
|
|
|
this.hunspellWorker.postMessage(message)
|
|
|
|
this.pendingMessages.length = 0
|
|
|
|
}
|
2024-10-11 05:24:17 -04:00
|
|
|
} else if (rest.loaded) {
|
2024-10-02 04:25:02 -04:00
|
|
|
this.loaded = true
|
2024-10-11 05:24:17 -04:00
|
|
|
} else if (rest.loadingFailed) {
|
|
|
|
captureException(new Error('Spell check loading failed'), {
|
2024-10-16 06:54:35 -04:00
|
|
|
tags: { ol_spell_check_language: this.language },
|
2024-10-11 05:24:17 -04:00
|
|
|
})
|
|
|
|
this.loadingFailed = true
|
|
|
|
this.pendingMessages.length = 0
|
2024-10-02 04:25:02 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|