Move status icon out of core grammar checking service

Status icon is user-implemented now.
This commit is contained in:
2024-06-27 11:35:40 +02:00
parent 042a6b288c
commit 4e3559408e
14 changed files with 264 additions and 142 deletions

View File

@@ -38,10 +38,12 @@ class BesService {
* @param {Element} textElement The element in DOM tree that hosts coordinate-measurable clone of
* the text to proof. Same as hostElement for <div>, separate for
* <textarea> and <input> hosts.
* @param {*} eventSink Event sink for notifications
*/
constructor(hostElement, textElement) {
constructor(hostElement, textElement, eventSink) {
this.hostElement = hostElement
this.textElement = textElement
this.eventSink = eventSink
this.results = [] // Results of grammar-checking, one per each block/paragraph of text
this.createCorrectionPanel()
@@ -83,19 +85,20 @@ class BesService {
* BesCKService.register for that.
*
* @param {Element} hostElement Host element
* @param {*} eventSink Event sink for notifications
* @returns Grammar checking service registered for given DOM element; unfedined if no service
* registered.
*/
static registerByElement(hostElement) {
static registerByElement(hostElement, eventSink) {
if (hostElement.tagName === 'TEXTAREA') {
return BesTAService.register(hostElement)
return BesTAService.register(hostElement, eventSink)
} else if (
hostElement.getAttribute('contenteditable')?.toLowerCase() ===
'plaintext-only'
) {
return BesDOMPlainTextService.register(hostElement)
return BesDOMPlainTextService.register(hostElement, eventSink)
} else {
return BesDOMService.register(hostElement)
return BesDOMService.register(hostElement, eventSink)
}
}
@@ -116,6 +119,8 @@ class BesService {
)
this.hostElement.spellcheck = this.originalSpellcheck
this.clearCorrectionPanel()
if (this.eventSink && 'unregister' in this.eventSink)
this.eventSink.unregister(this)
}
/**
@@ -145,8 +150,9 @@ class BesService {
this.proofingCount = 1 // 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.updateStatusIcon('bes-status-loading', 'Besana preverja pravopis.')
this.abortController = new AbortController()
if (this.eventSink && 'startProofing' in this.eventSink)
this.eventSink.startProofing(this)
}
/**
@@ -154,6 +160,8 @@ class BesService {
*/
onProofing() {
this.proofingCount++
if (this.eventSink && 'proofing' in this.eventSink)
this.eventSink.proofing(this)
}
/**
@@ -165,10 +173,9 @@ class BesService {
*/
onFailedProofing(response) {
delete this.abortController
this.updateStatusIcon(
'bes-status-error',
`Pri preverjanju pravopisa je prišlo do napake ${response.status} ${response.statusText}.`
)
console.log(`Grammar checking failed: ${response.status} ${response.statusText}`)
if (this.eventSink && 'failedProofing' in this.eventSink)
this.eventSink.failedProofing(this, response)
}
/**
@@ -177,8 +184,13 @@ class BesService {
* @param {Error} error Error
*/
onFailedProofingResult(error) {
if (error !== 'AbortError' && !this.proofingError)
this.proofingError = error
if (error !== 'AbortError') {
if (!this.proofingError)
this.proofingError = error
console.log(`Failed to parse grammar checking results: ${error}`)
}
if (this.eventSink && 'failedProofingResult' in this.eventSink)
this.eventSink.failedProofingResult(this, error)
if (--this.proofingCount <= 0) this.onEndProofing()
}
@@ -189,6 +201,8 @@ class BesService {
*/
onProofingProgress(numberOfMatches) {
this.proofingMatches += numberOfMatches
if (this.eventSink && 'proofingProgress' in this.eventSink)
this.eventSink.proofingProgress(this)
if (--this.proofingCount <= 0) this.onEndProofing()
}
@@ -197,17 +211,8 @@ class BesService {
*/
onEndProofing() {
delete this.abortController
if (this.proofingError) {
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.')
if (this.eventSink && 'endProofing' in this.eventSink)
this.eventSink.endProofing(this)
}
/**
@@ -229,7 +234,8 @@ class BesService {
*/
onReposition() {
this.setCorrectionPanelSize()
this.setStatusDivPosition()
if (this.eventSink && 'reposition' in this.eventSink)
this.eventSink.reposition(this)
}
/**
@@ -237,11 +243,9 @@ class BesService {
*/
onResize() {
this.setCorrectionPanelSize()
this.setStatusDivPosition()
// When window is resized, host element might resize too.
// This may cause text to re-wrap requiring markup repositioning.
this.repositionAllMarkup()
if (this.eventSink && 'resize' in this.eventSink)
this.eventSink.resize(this)
}
/**
@@ -323,17 +327,6 @@ class BesService {
document.body.insertBefore(panelParent, document.body.firstChild)
this.setCorrectionPanelSize()
}
this.statusDiv = document.createElement('div')
this.statusDiv.classList.add('bes-status-div')
this.statusIcon = document.createElement('div')
this.statusIcon.classList.add('bes-status-icon')
this.statusDiv.appendChild(this.statusIcon)
this.setStatusDivPosition()
this.textElement.parentNode.insertBefore(
this.statusDiv,
this.textElement.nextSibling
)
}
/**
@@ -342,8 +335,6 @@ class BesService {
clearCorrectionPanel() {
this.correctionPanel.remove()
this.scrollPanel.remove()
this.statusDiv.remove()
this.statusIcon.remove()
}
/**
@@ -447,39 +438,6 @@ class BesService {
this.proofAll()
}
/**
* Repositions status DIV element.
*/
setStatusDivPosition() {
const rect = this.textElement.getBoundingClientRect()
const scrollbarWidth =
this.textElement.offsetWidth - this.textElement.clientWidth
this.statusDiv.style.left = `${rect.right - 40 - scrollbarWidth}px`
const scrollbarHeight =
this.textElement.offsetHeight - this.textElement.clientHeight
this.statusDiv.style.top = `${rect.bottom - 30 - scrollbarHeight}px`
}
/**
* Sets status icon style and title.
*
* @param {String} status CSS class name to set status icon to
* @param {String} title Title of the status icon
*/
updateStatusIcon(status, title) {
const statuses = [
'bes-status-loading',
'bes-status-success',
'bes-status-mistakes',
'bes-status-error'
]
statuses.forEach(statusClass => {
this.statusIcon.classList.remove(statusClass)
})
this.statusIcon.classList.add(status)
this.statusDiv.title = title
}
/**
* Tests if host element is inline
*
@@ -520,9 +478,10 @@ class BesTreeService extends BesService {
* @param {Element} textElement The element in DOM tree that hosts coordinate-measurable clone of
* the text to proof. Same as hostElement for <div>, separate for
* <textarea> and <input> hosts.
* @param {*} eventSink Event sink for notifications
*/
constructor(hostElement, textElement) {
super(hostElement, textElement)
constructor(hostElement, textElement, eventSink) {
super(hostElement, textElement, eventSink)
this.onClick = this.onClick.bind(this)
this.textElement.addEventListener('click', this.onClick)
}
@@ -891,9 +850,10 @@ class BesDOMService extends BesTreeService {
*
* @param {Element} hostElement The element in DOM tree we are providing grammar-checking service
* for
* @param {*} eventSink Event sink for notifications
*/
constructor(hostElement) {
super(hostElement, hostElement)
constructor(hostElement, eventSink) {
super(hostElement, hostElement, eventSink)
this.onBeforeInput = this.onBeforeInput.bind(this)
this.hostElement.addEventListener('beforeinput', this.onBeforeInput)
this.onInput = this.onInput.bind(this)
@@ -904,12 +864,15 @@ class BesDOMService extends BesTreeService {
* Registers grammar checking service.
*
* @param {Element} hostElement DOM element to register grammar checking service for
* @param {*} eventSink Event sink for notifications
* @returns {BesDOMService} Grammar checking service instance
*/
static register(hostElement) {
static register(hostElement, eventSink) {
let service = BesService.getServiceByElement(hostElement)
if (service) return service
service = new BesDOMService(hostElement)
service = new BesDOMService(hostElement, eventSink)
if (service.eventSink && 'register' in service.eventSink)
service.eventSink.register(service)
service.proofAll()
return service
}
@@ -974,9 +937,10 @@ class BesCKService extends BesTreeService {
* @param {Element} hostElement The element in DOM tree we are providing grammar-checking service
* for
* @param {*} ckEditorInstance CKEditor instance
* @param {*} eventSink Event sink for notifications
*/
constructor(hostElement, ckEditorInstance) {
super(hostElement, hostElement)
constructor(hostElement, ckEditorInstance, eventSink) {
super(hostElement, hostElement, eventSink)
this.ckEditorInstance = ckEditorInstance
this.disableCKEditorSpellcheck()
this.onChangeData = this.onChangeData.bind(this)
@@ -988,12 +952,15 @@ class BesCKService extends BesTreeService {
*
* @param {Element} hostElement DOM element to register grammar checking service for
* @param {CKEditorInstance} ckEditorInstance Enable CKEditor tweaks
* @param {*} eventSink Event sink for notifications
* @returns {BesCKService} Grammar checking service instance
*/
static register(hostElement, ckEditorInstance) {
static register(hostElement, ckEditorInstance, eventSink) {
let service = BesService.getServiceByElement(hostElement)
if (service) return service
service = new BesCKService(hostElement, ckEditorInstance)
service = new BesCKService(hostElement, ckEditorInstance, eventSink)
if (service.eventSink && 'register' in service.eventSink)
service.eventSink.register(service)
service.proofAll()
return service
}
@@ -1104,14 +1071,6 @@ class BesCKService extends BesTreeService {
this.proofAll()
}
/**
* Repositions status DIV element.
*/
setStatusDivPosition() {
this.statusDiv.style.right = `10px`
this.statusDiv.style.bottom = `10px`
}
/**
* Tests if host element is inline
*
@@ -1139,9 +1098,10 @@ class BesPlainTextService extends BesService {
* @param {Element} textElement The element in DOM tree that hosts coordinate-measurable clone of
* the text to proof. Same as hostElement for <div>, separate for
* <textarea> and <input> hosts.
* @param {*} eventSink Event sink for notifications
*/
constructor(hostElement, textElement) {
super(hostElement, textElement)
constructor(hostElement, textElement, eventSink) {
super(hostElement, textElement, eventSink)
this.reEOP = /(\r?\n){2,}/g
this.onBeforeInput = this.onBeforeInput.bind(this)
this.hostElement.addEventListener('beforeinput', this.onBeforeInput)
@@ -1459,21 +1419,25 @@ class BesDOMPlainTextService extends BesPlainTextService {
*
* @param {Element} hostElement The element in DOM tree we are providing grammar-checking service
* for
* @param {*} eventSink Event sink for notifications
*/
constructor(hostElement) {
super(hostElement, hostElement)
constructor(hostElement, eventSink) {
super(hostElement, hostElement, eventSink)
}
/**
* Registers grammar checking service.
*
* @param {Element} hostElement DOM element to register grammar checking service for
* @param {*} eventSink Event sink for notifications
* @returns {BesDOMPlainTextService} Grammar checking service instance
*/
static register(hostElement) {
static register(hostElement, eventSink) {
let service = BesService.getServiceByElement(hostElement)
if (service) return service
service = new BesDOMPlainTextService(hostElement)
service = new BesDOMPlainTextService(hostElement, eventSink)
if (service.eventSink && 'register' in service.eventSink)
service.eventSink.register(service)
service.proofAll()
return service
}
@@ -1594,9 +1558,10 @@ class BesTAService extends BesPlainTextService {
*
* @param {Element} hostElement The element in DOM tree we are providing grammar-checking service
* for
* @param {*} eventSink Event sink for notifications
*/
constructor(hostElement) {
super(hostElement, BesTAService.createTextElement(hostElement))
constructor(hostElement, eventSink) {
super(hostElement, BesTAService.createTextElement(hostElement), eventSink)
this.textElement.replaceChildren(
document.createTextNode(this.hostElement.value)
)
@@ -1606,12 +1571,15 @@ class BesTAService extends BesPlainTextService {
* Registers grammar checking service.
*
* @param {Element} hostElement DOM element to register grammar checking service for
* @param {*} eventSink Event sink for notifications
* @returns {BesTAService} Grammar checking service instance
*/
static register(hostElement) {
static register(hostElement, eventSink) {
let service = BesService.getServiceByElement(hostElement)
if (service) return service
service = new BesTAService(hostElement)
service = new BesTAService(hostElement, eventSink)
if (service.eventSink && 'register' in service.eventSink)
service.eventSink.register(service)
service.proofAll()
return service
}