Add some more documentation, upgrade addMistakeMarkup for multiline

This commit is contained in:
Simon Rozman 2024-03-12 12:58:26 +01:00
parent bc0a3f7905
commit 259fc25f6f

View File

@ -158,13 +158,11 @@ class BesEditor {
}
}
const { clientRects, highlight } = this.addMistakeMarkup(
range,
this.scrollPanel
)
const { clientRects, highlights } =
this.addMistakeMarkup(range)
matches.push({
rects: clientRects,
highlight: highlight,
highlights: highlights,
range: range,
match: match
})
@ -238,9 +236,8 @@ class BesEditor {
)
})
blockElements.forEach(block => {
editor.clearProofed(block)
editor.clearMistakeMarkup(block)
editor.clearChildren(block)
editor.removeChild(block)
})
// Not a nice way to do it, but it works for now the repositionMistakes function is called before the DOM updates are finished.
setTimeout(() => {
@ -251,78 +248,84 @@ class BesEditor {
}, 1000)
}
// Test if given block element has already been grammar-proofed.
/**
* Tests if given block element has already been grammar-proofed.
*
* @param {Element} el DOM element to check
* @returns {Boolean} true if the element has already been grammar-proofed; false otherwise.
*/
isProofed(el) {
let filteredChildren = this.children.filter(child => child.elements === el)
return filteredChildren[0]?.isProofed
return this.children.find(child => child.elements === el)?.isProofed
}
// Mark given block element as grammar-proofed.
/**
* Marks given block element as grammar-proofed.
*
* @param {Element} el DOM element that was checked
* @param {Array} matches Grammar mistakes
*/
markProofed(el, matches) {
let newChild = {
this.removeChild(el)
this.children.push({
isProofed: true,
elements: el, // TODO: Rename "elements" to "el" - 1. It contains only single element (plural elements is misleading), 2. BesEditor also uses "el" named field for DOM matching.
matches: matches
}
this.children = this.children.map(child =>
child.elements === newChild.elements ? newChild : child
)
if (!this.children.some(child => child.elements === newChild.elements)) {
this.children.push(newChild)
}
})
}
// Mark given block element as not grammar-proofed.
clearProofed(el) {
this.children
.filter(child => child.elements === el)
.forEach(child => {
child.isProofed = false
})
}
// Remove all grammar mistakes markup for given block element.
/**
* Clears given block element as not grammar-proofed and removes all its grammar mistakes.
*
* @param {Element} el DOM element that we should re-grammar-proof
*/
clearMistakeMarkup(el) {
this.children
.filter(child => child.elements === el)
.forEach(child => {
child.matches.forEach(match => {
match.highlight.remove()
delete match.highlight
})
})
let child = this.children.find(child => child.elements === el)
if (!child) return
child.isProofed = false
child.matches.forEach(match => {
match.highlights.forEach(h => h.remove())
delete match.highlights
})
}
// Remove all children from this.children array
clearChildren(el) {
if (el?.classList.contains('bes-online-editor')) return
else this.children = this.children.filter(child => child.elements !== el)
/**
* Removes given block element from this.children array
*
* @param {Element} el DOM element for removal
*/
removeChild(el) {
this.children = this.children.filter(child => child.elements !== el)
}
/**
* Updates grammar mistake markup positions.
*/
repositionMistakes() {
this.children.forEach(child => {
this.clearMistakeMarkup(child.elements)
child.matches.forEach(match => {
const { clientRects, highlight } = this.addMistakeMarkup(
match.range,
this.scrollPanel
)
const { clientRects, highlights } = this.addMistakeMarkup(match.range)
match.rects = clientRects
match.highlight = highlight
match.highlights = highlights
})
})
}
// Adds grammar mistake markup
addMistakeMarkup(range, scrollPanel) {
/**
* Adds grammar mistake markup.
*
* @param {Range} range Grammar mistake range
* @returns {Object} Client rectangles and grammar mistake highlight elements
*/
addMistakeMarkup(range) {
// TODO: Consider using range.getClientRects() instead of range.getBoundingClientRect()
// In CKEditor case, the highlight element is not shown for some reason. But after resizing the window it is shown.
const clientRects = range.getClientRects()
const scrollPanelRect = scrollPanel.getBoundingClientRect()
const highlight = document.createElement('div')
const scrollPanelRect = this.scrollPanel.getBoundingClientRect()
let highlights = []
for (let i = 0, n = clientRects.length; i < n; ++i) {
const rect = clientRects[i]
const highlight = document.createElement('div')
highlight.classList.add('bes-typo-mistake')
const topPosition = rect.top - scrollPanelRect.top
const leftPosition = rect.left - scrollPanelRect.left
@ -331,11 +334,17 @@ class BesEditor {
highlight.style.width = `${rect.width}px`
highlight.style.height = `${rect.height}px`
this.scrollPanel.appendChild(highlight)
highlights.push(highlight)
}
return { clientRects, highlight }
return { clientRects, highlights }
}
// Tests if given element is block element.
/**
* Tests if given element is block element.
*
* @param {Element} el DOM element
* @returns false if CSS display property is inline or inline-block; true otherwise.
*/
static isBlockElement(el) {
switch (
document.defaultView
@ -351,7 +360,12 @@ class BesEditor {
}
}
// Returns first block parent element
/**
* Returns first block parent element of a node.
*
* @param {Node} el DOM node
* @returns {Element} Innermost block element containing given node
*/
getBlockParent(el) {
for (; el && el !== this.el; el = el.parentNode) {
if (el.nodeType === Node.ELEMENT_NODE && BesEditor.isBlockElement(el))
@ -360,6 +374,12 @@ class BesEditor {
return el
}
/**
* Returns next node in the DOM text flow.
*
* @param {Node} node DOM node
* @returns {Node} Next node
*/
static getNextNode(node) {
if (node.firstChild) return node.firstChild
while (node) {
@ -368,6 +388,12 @@ class BesEditor {
}
}
/**
* Returns all ancestors of a node.
*
* @param {Node} node DOM node
* @returns {Array} Array of all ancestors in reverse order: node, immediate parent, ..., document
*/
static getParents(node) {
let parents = []
do {
@ -377,6 +403,12 @@ class BesEditor {
return parents.reverse()
}
/**
* Returns all nodes marked by a range.
*
* @param {Range} range DOM range
* @returns {Array} Array of nodes
*/
static getNodesInRange(range) {
var start = range.startContainer
var end = range.endContainer
@ -425,15 +457,7 @@ class BesEditor {
let editor = besEditors.find(e => e.el === edit)
if (!editor) return
const target = editor.getBlockParent(event.target)
const popup = document.querySelector('bes-popup-el')
const matches = editor.children.find(
child => child.elements === target
)?.matches
if (
!matches ||
!editor.renderPopup(target, matches, popup, event.clientX, event.clientY)
)
popup.hide()
editor.renderPopup(target, event.clientX, event.clientY)
}
/**
@ -456,13 +480,13 @@ class BesEditor {
}
/**
* Finds the editor with grammar checking service the given DOM node is a child of.
* Finds the editor with grammar checking service a DOM node is child of.
*
* @param {Node} target DOM node
* @param {Node} el DOM node
* @returns {Element} Editor DOM element; null if DOM node is not a descendant of an editor.
*/
static findParent(target) {
for (let el = target; el; el = el.parentNode) {
static findParent(el) {
for (; el; el = el.parentNode) {
if (el.classList?.contains('bes-online-editor')) {
return el
}
@ -470,24 +494,41 @@ class BesEditor {
return null
}
renderPopup(el, matches, popup, clientX, clientY) {
for (let m of matches) {
if (m.rects) {
for (let r of m.rects) {
if (BesEditor.isPointInRect(clientX, clientY, r)) {
popup.changeText(m.match.message)
m.match.replacements.forEach(replacement => {
popup.appendReplacements(el, r, m.match, replacement.value, this)
})
popup.show(clientX, clientY)
return true
/**
* Displays grammar mistake explanation popup.
*
* @param {*} el DOM element we have grammar proofing available for
* @param {*} clientX Client X coordinate of the pointer event
* @param {*} clientY Client Y coordinate of the pointer event
*/
renderPopup(el, clientX, clientY) {
const popup = document.querySelector('bes-popup-el')
const matches = this.children.find(
child => child.elements === el
)?.matches
if (matches) {
for (let m of matches) {
if (m.rects) {
for (let r of m.rects) {
if (BesEditor.isPointInRect(clientX, clientY, r)) {
popup.changeText(m.match.message)
m.match.replacements.forEach(replacement => {
popup.appendReplacements(
el,
r,
m.match,
replacement.value,
this
)
})
popup.show(clientX, clientY)
return
}
}
}
} else {
popup.hide()
}
}
return false
popup.hide()
}
// TODO: In rich HTML texts, match.offset has different value than in plain text.
@ -592,12 +633,9 @@ window.onresize = () => {
editor.children.forEach(child => {
editor.clearMistakeMarkup(child.elements)
child.matches.forEach(match => {
const { clientRects, highlight } = editor.addMistakeMarkup(
match.range,
editor.scrollPanel
)
const { clientRects, highlights } = editor.addMistakeMarkup(match.range)
match.rects = clientRects
match.highlight = highlight
match.highlights = highlights
})
})
})