service2: Rework proofing notifications and aborting

Notifications were inconsistent in case of errors while checking
grammar.
This commit is contained in:
Simon Rozman 2024-05-10 15:38:53 +02:00
parent e499ad22f8
commit 133a278c9c

View File

@ -1,12 +1,12 @@
// TODO: Port popup dialog from service.js // TODO: Port popup dialog from service.js
// TODO: Test with <div contenteditable="plaintext-only"> // TODO: Test with contenteditable="plaintext-only"
// TODO: Implement <textarea> class // TODO: Implement <textarea> class
// TODO: Port CKEditor class from service.js // TODO: Port CKEditor class from service.js
/** /**
* Collection of all grammar checking services in the document * Collection of all grammar checking services in the document
* *
* We dispatch all window messages to all services registered here. * We dispatch relevant window messages to all services registered here.
*/ */
let besServices = [] let besServices = []
@ -26,7 +26,6 @@ window.addEventListener('scroll', () =>
class BesService { class BesService {
constructor(hostElement) { constructor(hostElement) {
this.hostElement = hostElement this.hostElement = hostElement
this.abortController = new AbortController()
this.createCorrectionPanel() this.createCorrectionPanel()
// Disable browser built-in spell-checker to prevent collision with our grammar markup. // Disable browser built-in spell-checker to prevent collision with our grammar markup.
@ -47,7 +46,6 @@ class BesService {
this.hostElement.removeEventListener('scroll', this.handleScroll) this.hostElement.removeEventListener('scroll', this.handleScroll)
this.hostElement.spellcheck = this.originalSpellcheck this.hostElement.spellcheck = this.originalSpellcheck
this.clearCorrectionPanel() this.clearCorrectionPanel()
this.abortController.abort()
} }
/** /**
@ -55,6 +53,7 @@ class BesService {
*/ */
onStartProofing() { onStartProofing() {
this.proofingCount = 0 // Ref-count how many grammar-checking blocks of text are active this.proofingCount = 0 // Ref-count how many grammar-checking blocks of text are active
this.proofingError = null // The first non-fatal error in grammar-checking run
this.proofingMatches = 0 // Number of grammar mistakes detected in entire grammar-checking run this.proofingMatches = 0 // Number of grammar mistakes detected in entire grammar-checking run
this.updateStatusIcon('bes-status-loading', 'Besana preverja pravopis.') this.updateStatusIcon('bes-status-loading', 'Besana preverja pravopis.')
} }
@ -67,7 +66,9 @@ class BesService {
} }
/** /**
* Called when grammar-checking of a block of text failed (as 500 Internal server error, timeout, etc.) * Called when grammar-checking failed (as 500 Internal server error, timeout, etc.)
*
* This error is fatal and proofing will not continue.
* *
* @param {Response} response HTTP response * @param {Response} response HTTP response
*/ */
@ -84,11 +85,8 @@ class BesService {
* @param {Error} error Error * @param {Error} error Error
*/ */
onFailedProofingResult(error) { onFailedProofingResult(error) {
this.proofingCount-- if (!this.proofingError) this.proofingError = error
this.updateStatusIcon( if (--this.proofingCount <= 0) this.onEndProofing()
'bes-status-error',
`Pri obdelavi odgovora pravopisnega strežnika je prišlo do napake: ${error}`
)
} }
/** /**
@ -98,16 +96,24 @@ class BesService {
*/ */
onProofingProgress(numberOfMatches) { onProofingProgress(numberOfMatches) {
this.proofingMatches += numberOfMatches this.proofingMatches += numberOfMatches
if (--this.proofingCount <= 0) { if (--this.proofingCount <= 0) this.onEndProofing()
// This was the last block of text in the run we were waiting for. }
// TODO: If onFailedProofingResult was called on a non-last block of text, the below will override the status icon error state.
if (this.proofingMatches > 0) /**
this.updateStatusIcon( * Called when grammar-checking run is ended
'bes-status-mistakes', */
'Število napak: ' + this.proofingMatches onEndProofing() {
) if (this.proofingError) {
else this.updateStatusIcon('bes-status-success', 'V besedilu ni napak.') this.updateStatusIcon(
} 'bes-status-error',
`Pri obdelavi odgovora pravopisnega strežnika je prišlo do napake: ${this.proofingError}`
)
} else if (this.proofingMatches > 0)
this.updateStatusIcon(
'bes-status-mistakes',
`Število napak: ${this.proofingMatches}`
)
else this.updateStatusIcon('bes-status-success', 'V besedilu ni napak.')
} }
/** /**
@ -255,9 +261,39 @@ class BesDOMService extends BesService {
unregister() { unregister() {
this.hostElement.removeEventListener('input', this.handleInput) this.hostElement.removeEventListener('input', this.handleInput)
this.hostElement.removeEventListener('beforeinput', this.handleBeforeInput) this.hostElement.removeEventListener('beforeinput', this.handleBeforeInput)
if (this.timer) clearTimeout(this.timer)
if (this.abortController) this.abortController.abort()
super.unregister() super.unregister()
} }
/**
* Called initially when grammar-checking run is started
*/
onStartProofing() {
super.onStartProofing()
this.abortController = new AbortController()
}
/**
* Called when grammar-checking failed (as 500 Internal server error, timeout, etc.)
*
* This error is fatal and proofing will not continue.
*
* @param {Response} response HTTP response
*/
onFailedProofing(response) {
delete this.abortController
super.onFailedProofing(response)
}
/**
* Called when grammar-checking run is ended
*/
onEndProofing() {
delete this.abortController
super.onEndProofing()
}
/** /**
* Called to report scrolling * Called to report scrolling
*/ */
@ -293,9 +329,8 @@ class BesDOMService extends BesService {
* @param {InputEvent} event The event notifying the user of editable content changes * @param {InputEvent} event The event notifying the user of editable content changes
*/ */
onBeforeInput(event) { onBeforeInput(event) {
// Abort running grammar checking ASAP.
if (this.timer) clearTimeout(this.timer) if (this.timer) clearTimeout(this.timer)
this.abortController.abort() if (this.abortController) this.abortController.abort()
// Remove markup of all blocks of text that are about to change. // Remove markup of all blocks of text that are about to change.
let blockElements = new Set() let blockElements = new Set()
@ -320,7 +355,6 @@ class BesDOMService extends BesService {
// Defer grammar-checking to reduce stress on grammar-checking server. // Defer grammar-checking to reduce stress on grammar-checking server.
this.timer = setTimeout(() => { this.timer = setTimeout(() => {
this.abortController = new AbortController()
this.proofAll() this.proofAll()
delete this.timer delete this.timer
}, 1000) }, 1000)
@ -331,16 +365,22 @@ class BesDOMService extends BesService {
*/ */
proofAll() { proofAll() {
this.onStartProofing() this.onStartProofing()
this.proofNode(this.hostElement) this.proofNode(this.hostElement, this.abortController)
if (this.proofingCount == 0) {
// No text blocks were discovered for proofing. onProofingProgress() will not be called
// and we need to notify manually.
this.onEndProofing()
}
} }
/** /**
* Recursively grammar-checks a DOM node. * Recursively grammar-checks a DOM node.
* *
* @param {Node} node DOM root node to check * @param {Node} node DOM root node to check
* @param {AbortController} abortController Abort controller to cancel grammar-checking
* @returns {Array} Markup of text to check using BesStr * @returns {Array} Markup of text to check using BesStr
*/ */
proofNode(node) { proofNode(node, abortController) {
switch (node.nodeType) { switch (node.nodeType) {
case Node.TEXT_NODE: case Node.TEXT_NODE:
return [{ text: node.textContent, node: node, markup: false }] return [{ text: node.textContent, node: node, markup: false }]
@ -353,12 +393,11 @@ class BesDOMService extends BesService {
let data = [] let data = []
for (const el2 of node.childNodes) for (const el2 of node.childNodes)
data = data.concat(this.proofNode(el2)) data = data.concat(this.proofNode(el2, abortController))
if (data.some(x => !x.markup && !/^\s*$/.test(x.text))) { if (data.some(x => !x.markup && !/^\s*$/.test(x.text))) {
// Block element contains some text. // Block element contains some text.
this.onProofing() this.onProofing()
// Save the abort signal reference. It will change on grammar-check re-start. const signal = abortController.signal
const signal = this.abortController.signal
fetch( fetch(
new Request(besUrl + '/check', { new Request(besUrl + '/check', {
method: 'POST', method: 'POST',
@ -448,7 +487,7 @@ class BesDOMService extends BesService {
// Inline elements require no markup. Keep plain text only. // Inline elements require no markup. Keep plain text only.
let data = [] let data = []
for (const el2 of node.childNodes) for (const el2 of node.childNodes)
data = data.concat(this.proofNode(el2)) data = data.concat(this.proofNode(el2, abortController))
return data return data
} }
@ -693,6 +732,7 @@ class BesDOMService extends BesService {
// Auto-register all elements with bes-service class. // Auto-register all elements with bes-service class.
window.addEventListener('load', () => { window.addEventListener('load', () => {
document.querySelectorAll('.bes-service').forEach(hostElement => { document.querySelectorAll('.bes-service').forEach(hostElement => {
// TODO: Treat contenteditable="plaintext-only" separately. It requires manual paragraph splitting on \n\n.
if (hostElement.tagName === 'TEXTAREA') BesTAService.register(hostElement) if (hostElement.tagName === 'TEXTAREA') BesTAService.register(hostElement)
else BesDOMService.register(hostElement) else BesDOMService.register(hostElement)
}) })