Implement tab navigation for grammar mistakes
- The feature is still not fully finished yet. - Navigation does not work in Quill, textarea and static-content examples. - But it works well in CKEditor and contenteditable examples. #4
This commit is contained in:
parent
b9ab9b6a64
commit
c7c90101a2
82
service.js
82
service.js
@ -60,6 +60,8 @@ class BesService {
|
|||||||
this.hostElement.setAttribute('data-gramm', 'false')
|
this.hostElement.setAttribute('data-gramm', 'false')
|
||||||
this.hostElement.setAttribute('data-gramm_editor', 'false')
|
this.hostElement.setAttribute('data-gramm_editor', 'false')
|
||||||
this.hostElement.setAttribute('data-enable-grammarly', 'false')
|
this.hostElement.setAttribute('data-enable-grammarly', 'false')
|
||||||
|
this.onTab = this.onTab.bind(this)
|
||||||
|
this.hostElement.addEventListener('keydown', this.onTab)
|
||||||
|
|
||||||
this.onScroll = this.onScroll.bind(this)
|
this.onScroll = this.onScroll.bind(this)
|
||||||
this.hostElement.addEventListener('scroll', this.onScroll)
|
this.hostElement.addEventListener('scroll', this.onScroll)
|
||||||
@ -276,6 +278,9 @@ class BesService {
|
|||||||
*/
|
*/
|
||||||
onProofingProgress(numberOfMatches) {
|
onProofingProgress(numberOfMatches) {
|
||||||
this.proofingMatches += numberOfMatches
|
this.proofingMatches += numberOfMatches
|
||||||
|
// Sorting the array here is preferable to sorting only in onEndProofing.This way it allows users to interact
|
||||||
|
// with and navigate newly detected mistakes as soon as they appear.
|
||||||
|
this.sortMatchesArray()
|
||||||
if (this.eventSink && 'proofingProgress' in this.eventSink)
|
if (this.eventSink && 'proofingProgress' in this.eventSink)
|
||||||
this.eventSink.proofingProgress(this)
|
this.eventSink.proofingProgress(this)
|
||||||
if (--this.proofingCount <= 0) this.onEndProofing()
|
if (--this.proofingCount <= 0) this.onEndProofing()
|
||||||
@ -324,6 +329,19 @@ class BesService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called to report tab event
|
||||||
|
*
|
||||||
|
* @param {Event} e
|
||||||
|
*/
|
||||||
|
onTab(e) {
|
||||||
|
if (e.key === 'Tab' && this.highlightElements.length) {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
this.findNextMistake(e.shiftKey ? -1 : 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called to report repositioning
|
* Called to report repositioning
|
||||||
*/
|
*/
|
||||||
@ -1120,19 +1138,79 @@ class BesService {
|
|||||||
el.style.width = `${rect.width}px`
|
el.style.width = `${rect.width}px`
|
||||||
el.style.height = `${rect.height}px`
|
el.style.height = `${rect.height}px`
|
||||||
document.body.appendChild(el)
|
document.body.appendChild(el)
|
||||||
this.highlightElements.push(el)
|
const matchSorted =
|
||||||
|
this.sortedMatches.find(entry => entry.match === match) || null
|
||||||
|
this.highlightElements.push({ el, matchSorted })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function calculates / finds the next mistake.
|
||||||
|
* @param {Number} direction Navigation direction: 1 for next, -1 for previous
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
findNextMistake(direction = 1) {
|
||||||
|
if (!this.sortedMatches || !this.sortedMatches.length) return
|
||||||
|
const active = this.highlightElements.find(({ matchSorted }) => matchSorted)
|
||||||
|
let current = -1
|
||||||
|
if (active && active.matchSorted) {
|
||||||
|
current = this.sortedMatches.findIndex(
|
||||||
|
entry => entry.match === active.matchSorted.match
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const len = this.sortedMatches.length
|
||||||
|
const next = (current + direction + len) % len
|
||||||
|
this.activeMatchIndex = next
|
||||||
|
const { el, match } = this.sortedMatches[next]
|
||||||
|
|
||||||
|
// TODO: find out why scrollintoview does not work well
|
||||||
|
this.dismissPopup()
|
||||||
|
const popup = document.querySelector('bes-popup-el')
|
||||||
|
BesPopup.clearReplacements()
|
||||||
|
popup.setContent(el, match, this, this.isContentEditable())
|
||||||
|
this.highlightMistake(match)
|
||||||
|
popup.show(match.highlights[0].x, match.highlights[0].y)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clears highlight and hides popup
|
* Clears highlight and hides popup
|
||||||
*/
|
*/
|
||||||
dismissPopup() {
|
dismissPopup() {
|
||||||
BesPopup.hide()
|
BesPopup.hide()
|
||||||
this.highlightElements.forEach(el => el.remove())
|
this.highlightElements.forEach(obj => {
|
||||||
|
if (obj.el && typeof obj.el.remove === 'function') {
|
||||||
|
obj.el.remove()
|
||||||
|
}
|
||||||
|
})
|
||||||
this.highlightElements = []
|
this.highlightElements = []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function collects all matches from the results array, flattens them into a single array,
|
||||||
|
* and sorts them in order: first by their Y axis, then by X axis.
|
||||||
|
*/
|
||||||
|
sortMatchesArray() {
|
||||||
|
this.sortedMatches = []
|
||||||
|
this.results.forEach(element => {
|
||||||
|
element.matches.forEach(match => {
|
||||||
|
if (!match.highlights || !match.highlights.length) return
|
||||||
|
this.sortedMatches.push({
|
||||||
|
el: element.element,
|
||||||
|
match,
|
||||||
|
top: match.highlights[0].top
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
this.sortedMatches.sort((a, b) => {
|
||||||
|
if (a.top !== b.top) return a.top - b.top
|
||||||
|
|
||||||
|
const aLeft = a.match.highlights[0].left
|
||||||
|
const bLeft = b.match.highlights[0].left
|
||||||
|
return aLeft - bLeft
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if host element content is editable.
|
* Checks if host element content is editable.
|
||||||
*
|
*
|
||||||
|
Loading…
x
Reference in New Issue
Block a user