mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Improve spellcheck error handling and types (#21012)
GitOrigin-RevId: 0227122154f139401793aae2c8ca9c9960fe5e98
This commit is contained in:
parent
0131178491
commit
ecb94d6525
5 changed files with 136 additions and 52 deletions
|
@ -19,6 +19,7 @@ import { SpellChecker } from './spellchecker'
|
|||
import { parserWatcher } from '../wait-for-parser'
|
||||
import type { HunspellManager } from '@/features/source-editor/hunspell/HunspellManager'
|
||||
import { debugConsole } from '@/utils/debugging'
|
||||
import { captureException } from '@/infrastructure/error-reporter'
|
||||
|
||||
type Options = {
|
||||
spellCheckLanguage?: string
|
||||
|
@ -81,9 +82,15 @@ const spellCheckerField = StateField.define<SpellChecker | null>({
|
|||
)
|
||||
: null
|
||||
} else if (effect.is(addIgnoredWord)) {
|
||||
value?.addWord(effect.value.text).catch(debugConsole.error)
|
||||
value?.addWord(effect.value.text).catch(error => {
|
||||
captureException(error)
|
||||
debugConsole.error(error)
|
||||
})
|
||||
} else if (effect.is(removeIgnoredWord)) {
|
||||
value?.removeWord(effect.value.text).catch(debugConsole.error)
|
||||
value?.removeWord(effect.value.text).catch(error => {
|
||||
captureException(error)
|
||||
debugConsole.error(error)
|
||||
})
|
||||
}
|
||||
}
|
||||
return value
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
import { waitForParser } from '../wait-for-parser'
|
||||
import { debugConsole } from '@/utils/debugging'
|
||||
import type { HunspellManager } from '../../hunspell/HunspellManager'
|
||||
import { captureException } from '@/infrastructure/error-reporter'
|
||||
|
||||
/*
|
||||
* Spellchecker, handles updates, schedules spelling checks
|
||||
|
@ -120,10 +121,14 @@ export class SpellChecker {
|
|||
type: 'spell',
|
||||
words: unknownWords.map(word => word.text),
|
||||
},
|
||||
(result: any) => {
|
||||
result => {
|
||||
if (!signal.aborted) {
|
||||
if (result.error) {
|
||||
if ('error' in result) {
|
||||
debugConsole.error(result.error)
|
||||
captureException(
|
||||
new Error('Error running spellcheck for word'),
|
||||
{ language: this.language }
|
||||
)
|
||||
} else {
|
||||
processResult(result.misspellings)
|
||||
}
|
||||
|
@ -148,10 +153,10 @@ export class SpellChecker {
|
|||
return new Promise<{ suggestions: string[] }>((resolve, reject) => {
|
||||
if (this.hunspellManager) {
|
||||
this.hunspellManager.send({ type: 'suggest', word }, result => {
|
||||
if ((result as { error: true }).error) {
|
||||
reject(new Error())
|
||||
if ('error' in result) {
|
||||
reject(new Error('Error finding spelling suggestions for word'))
|
||||
} else {
|
||||
resolve(result as { suggestions: string[] })
|
||||
resolve(result)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -162,8 +167,8 @@ export class SpellChecker {
|
|||
return new Promise<void>((resolve, reject) => {
|
||||
if (this.hunspellManager) {
|
||||
this.hunspellManager.send({ type: 'add_word', word }, result => {
|
||||
if ((result as { error: true }).error) {
|
||||
reject(new Error())
|
||||
if ('error' in result) {
|
||||
reject(new Error('Error adding word to spellcheck'))
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
|
@ -176,8 +181,8 @@ export class SpellChecker {
|
|||
return new Promise<void>((resolve, reject) => {
|
||||
if (this.hunspellManager) {
|
||||
this.hunspellManager.send({ type: 'remove_word', word }, result => {
|
||||
if ((result as { error: true }).error) {
|
||||
reject(new Error())
|
||||
if ('error' in result) {
|
||||
reject(new Error('Error removing word from spellcheck'))
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
|
|
|
@ -13,6 +13,8 @@ import classnames from 'classnames'
|
|||
import { useFeatureFlag } from '@/shared/context/split-test-context'
|
||||
import { sendMB } from '@/infrastructure/event-tracking'
|
||||
import SpellingSuggestionsFeedback from './spelling-suggestions-feedback'
|
||||
import { captureException } from '@/infrastructure/error-reporter'
|
||||
import { debugConsole } from '@/utils/debugging'
|
||||
|
||||
const ITEMS_TO_SHOW = 8
|
||||
|
||||
|
@ -51,7 +53,9 @@ export const SpellingSuggestions: FC<{
|
|||
|
||||
useEffect(() => {
|
||||
if (!word.suggestions) {
|
||||
spellChecker?.suggest(word.text).then(result => {
|
||||
spellChecker
|
||||
?.suggest(word.text)
|
||||
.then(result => {
|
||||
setSuggestions(result.suggestions.slice(0, ITEMS_TO_SHOW))
|
||||
setWaiting(false)
|
||||
sendMB('spelling-suggestion-shown', {
|
||||
|
@ -60,6 +64,12 @@ export const SpellingSuggestions: FC<{
|
|||
// word: transaction.state.sliceDoc(mark.from, mark.to),
|
||||
})
|
||||
})
|
||||
.catch(error => {
|
||||
captureException(error, {
|
||||
language: spellCheckLanguage,
|
||||
})
|
||||
debugConsole.error(error)
|
||||
})
|
||||
}
|
||||
}, [word, spellChecker, spellCheckLanguage])
|
||||
|
||||
|
|
|
@ -1,33 +1,61 @@
|
|||
/* eslint-disable no-dupe-class-members */
|
||||
|
||||
import { v4 as uuid } from 'uuid'
|
||||
import getMeta from '@/utils/meta'
|
||||
import { debugConsole } from '@/utils/debugging'
|
||||
import { captureException } from '@/infrastructure/error-reporter'
|
||||
|
||||
type Message =
|
||||
| {
|
||||
id?: string
|
||||
type SpellMessage = {
|
||||
type: 'spell'
|
||||
words: string[]
|
||||
}
|
||||
| {
|
||||
id?: string
|
||||
|
||||
type SuggestMessage = {
|
||||
type: 'suggest'
|
||||
word: string
|
||||
}
|
||||
| {
|
||||
id?: string
|
||||
|
||||
type AddWordMessage = {
|
||||
type: 'add_word'
|
||||
word: string
|
||||
}
|
||||
| {
|
||||
id?: string
|
||||
|
||||
type RemoveWordMessage = {
|
||||
type: 'remove_word'
|
||||
word: string
|
||||
}
|
||||
| {
|
||||
id?: 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)
|
||||
|
||||
export class HunspellManager {
|
||||
baseAssetPath: string
|
||||
dictionariesRoot: string
|
||||
|
@ -35,8 +63,9 @@ export class HunspellManager {
|
|||
abortController: AbortController | undefined
|
||||
listening = false
|
||||
loaded = false
|
||||
loadingFailed = false
|
||||
pendingMessages: Message[] = []
|
||||
callbacks: Map<string, (value: unknown) => void> = new Map()
|
||||
callbacks: Map<string, ResultCallback> = new Map()
|
||||
|
||||
constructor(
|
||||
private readonly language: string,
|
||||
|
@ -64,8 +93,36 @@ export class HunspellManager {
|
|||
})
|
||||
}
|
||||
|
||||
send(message: Message, callback: (value: unknown) => void) {
|
||||
debugConsole.log(message)
|
||||
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
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
message.id = uuid()
|
||||
this.callbacks.set(message.id, callback)
|
||||
|
@ -80,14 +137,14 @@ export class HunspellManager {
|
|||
|
||||
receive(event: MessageEvent) {
|
||||
debugConsole.log(event.data)
|
||||
const { id, listening, loaded, ...rest } = event.data
|
||||
const { id, ...rest } = event.data
|
||||
if (id) {
|
||||
const callback = this.callbacks.get(id)
|
||||
if (callback) {
|
||||
this.callbacks.delete(id)
|
||||
callback(rest)
|
||||
}
|
||||
} else if (listening) {
|
||||
} else if (rest.listening) {
|
||||
this.listening = true
|
||||
this.hunspellWorker.postMessage({
|
||||
type: 'init',
|
||||
|
@ -100,9 +157,14 @@ export class HunspellManager {
|
|||
this.hunspellWorker.postMessage(message)
|
||||
this.pendingMessages.length = 0
|
||||
}
|
||||
} else if (loaded) {
|
||||
} else if (rest.loaded) {
|
||||
this.loaded = true
|
||||
// TODO: use this to display pending state?
|
||||
} else if (rest.loadingFailed) {
|
||||
captureException(new Error('Spell check loading failed'), {
|
||||
language: this.language,
|
||||
})
|
||||
this.loadingFailed = true
|
||||
this.pendingMessages.length = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -171,7 +171,7 @@ self.addEventListener('message', async event => {
|
|||
self.postMessage({ loaded: true })
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
self.postMessage({ error: true })
|
||||
self.postMessage({ loadingFailed: true })
|
||||
}
|
||||
break
|
||||
|
||||
|
|
Loading…
Reference in a new issue